{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "283025d5",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "73bd3553",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import gc\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import re\n",
    "import time\n",
    "import logging\n",
    "import seaborn as sns\n",
    "from scipy import stats\n",
    "import matplotlib.pyplot as plt\n",
    "import category_encoders as ce\n",
    "import networkx as nx\n",
    "import pickle\n",
    "import lightgbm as lgb\n",
    "import catboost as cat\n",
    "import xgboost as xgb\n",
    "from datetime import timedelta\n",
    "from gensim.models import Word2Vec\n",
    "from io import StringIO\n",
    "from tqdm import tqdm\n",
    "from lightgbm import LGBMClassifier\n",
    "from lightgbm import log_evaluation, early_stopping\n",
    "from sklearn.metrics import roc_curve\n",
    "from scipy.stats import chi2_contingency, pearsonr\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n",
    "from sklearn.feature_extraction import FeatureHasher\n",
    "from sklearn.model_selection import StratifiedKFold, KFold, train_test_split, GridSearchCV\n",
    "from category_encoders import TargetEncoder\n",
    "from sklearn.decomposition import TruncatedSVD\n",
    "from autogluon.tabular import TabularDataset, TabularPredictor, FeatureMetadata\n",
    "from autogluon.features.generators import AsTypeFeatureGenerator, BulkFeatureGenerator, DropUniqueFeatureGenerator, FillNaFeatureGenerator, PipelineFeatureGenerator\n",
    "from autogluon.features.generators import CategoryFeatureGenerator, IdentityFeatureGenerator, AutoMLPipelineFeatureGenerator\n",
    "from autogluon.common.features.types import R_INT, R_FLOAT\n",
    "from autogluon.core.metrics import make_scorer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8440031f",
   "metadata": {},
   "source": [
    "# 数据加载"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "cf7f31e8",
   "metadata": {},
   "source": [
    "## 训练集目标客户加载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ca3bad1e",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATA_DAT</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>FLAG</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20250630</td>\n",
       "      <td>2faac6549cf552d128f8bec626c99240</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20250630</td>\n",
       "      <td>42767ba2f4c8963e17fbf91e5007b626</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20250630</td>\n",
       "      <td>af02816658b8211ca6128b2239feb8ac</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20250630</td>\n",
       "      <td>cb9e2a9ace6608fb51cb68632780362d</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20250630</td>\n",
       "      <td>2c0feec8fa0ffb75831f566d0797a8b4</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   DATA_DAT                           CUST_NO  FLAG\n",
       "0  20250630  2faac6549cf552d128f8bec626c99240     1\n",
       "1  20250630  42767ba2f4c8963e17fbf91e5007b626     1\n",
       "2  20250630  af02816658b8211ca6128b2239feb8ac     1\n",
       "3  20250630  cb9e2a9ace6608fb51cb68632780362d     1\n",
       "4  20250630  2c0feec8fa0ffb75831f566d0797a8b4     1"
      ]
     },
     "execution_count": 3,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "train_target_cust = pd.read_csv('../Train_Data/TRAIN_TARGET_INFO.csv')\n",
    "train_target_cust.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9450f7e7",
   "metadata": {},
   "source": [
    "## 测试集目标客户加载"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "d0e83452",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATA_DAT</th>\n",
       "      <th>CUST_NO</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20250731</td>\n",
       "      <td>13ef4241a1959ccbcf8d8a30f0ed9d50</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20250731</td>\n",
       "      <td>029dede087234ee034590abefc4731a9</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20250731</td>\n",
       "      <td>929838a9271aa18da0ad8cb5154ce591</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20250731</td>\n",
       "      <td>51b04b6d47643e0f5303c38a429557d5</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20250731</td>\n",
       "      <td>a54db8a4f36e43e9390cf3e43d45f308</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   DATA_DAT                           CUST_NO\n",
       "0  20250731  13ef4241a1959ccbcf8d8a30f0ed9d50\n",
       "1  20250731  029dede087234ee034590abefc4731a9\n",
       "2  20250731  929838a9271aa18da0ad8cb5154ce591\n",
       "3  20250731  51b04b6d47643e0f5303c38a429557d5\n",
       "4  20250731  a54db8a4f36e43e9390cf3e43d45f308"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "test_target_cust = pd.read_csv('../DATA/A_TARGET.csv')\n",
    "test_target_cust.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "850cb671",
   "metadata": {},
   "source": [
    "## 特征文件加载"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eef572c1",
   "metadata": {},
   "source": [
    "### 通用pkl加载函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "1e262d1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "class FeatureLoader:\n",
    "    \"\"\"\n",
    "    特征文件加载器\n",
    "    支持批量加载feature目录下的所有pkl文件\n",
    "    \"\"\"\n",
    "    \n",
    "    def __init__(self, feature_dir='./feature'):\n",
    "        \"\"\"\n",
    "        初始化加载器\n",
    "        \n",
    "        参数:\n",
    "        - feature_dir: 特征文件目录路径\n",
    "        \"\"\"\n",
    "        self.feature_dir = feature_dir\n",
    "        self.features_dict = {}\n",
    "        self.feature_info = {}\n",
    "        \n",
    "    def load_single_feature(self, file_path):\n",
    "        \"\"\"\n",
    "        加载单个pkl特征文件\n",
    "        \n",
    "        参数:\n",
    "        - file_path: pkl文件路径\n",
    "        \n",
    "        返回:\n",
    "        - DataFrame: 特征数据\n",
    "        \"\"\"\n",
    "        try:\n",
    "            with open(file_path, 'rb') as f:\n",
    "                data = pickle.load(f)\n",
    "            \n",
    "            if not isinstance(data, pd.DataFrame):\n",
    "                raise ValueError(f\"文件 {file_path} 不是DataFrame格式\")\n",
    "            \n",
    "            return data\n",
    "        except Exception as e:\n",
    "            print(f\"加载文件 {file_path} 失败: {str(e)}\")\n",
    "            return None\n",
    "    \n",
    "    def load_all_features(self, pattern='*.pkl'):\n",
    "        \"\"\"\n",
    "        批量加载所有特征文件\n",
    "        \n",
    "        参数:\n",
    "        - pattern: 文件匹配模式\n",
    "        \n",
    "        返回:\n",
    "        - dict: {文件名: DataFrame}\n",
    "        \"\"\"\n",
    "        if not os.path.exists(self.feature_dir):\n",
    "            print(f\"目录不存在: {self.feature_dir}\")\n",
    "            return {}\n",
    "        \n",
    "        pkl_files = [f for f in os.listdir(self.feature_dir) if f.endswith('.pkl')]\n",
    "        \n",
    "        if not pkl_files:\n",
    "            print(f\"未找到pkl文件在目录: {self.feature_dir}\")\n",
    "            return {}\n",
    "        \n",
    "        print(f\"发现 {len(pkl_files)} 个特征文件\")\n",
    "        print(\"=\"*80)\n",
    "        \n",
    "        for pkl_file in pkl_files:\n",
    "            file_path = os.path.join(self.feature_dir, pkl_file)\n",
    "            file_name = os.path.splitext(pkl_file)[0]\n",
    "            \n",
    "            print(f\"\\n正在加载: {pkl_file}\")\n",
    "            data = self.load_single_feature(file_path)\n",
    "            \n",
    "            if data is not None:\n",
    "                self.features_dict[file_name] = data\n",
    "                \n",
    "                # 记录文件信息\n",
    "                self.feature_info[file_name] = {\n",
    "                    'file_path': file_path,\n",
    "                    'file_size_mb': os.path.getsize(file_path) / 1024 / 1024,\n",
    "                    'shape': data.shape,\n",
    "                    'memory_mb': data.memory_usage(deep=True).sum() / 1024 / 1024,\n",
    "                    'columns': data.columns.tolist()\n",
    "                }\n",
    "                \n",
    "                print(f\"  - 形状: {data.shape}\")\n",
    "                print(f\"  - 文件大小: {self.feature_info[file_name]['file_size_mb']:.2f} MB\")\n",
    "                print(f\"  - 内存占用: {self.feature_info[file_name]['memory_mb']:.2f} MB\")\n",
    "        \n",
    "        print(\"\\n\" + \"=\"*80)\n",
    "        print(f\"成功加载 {len(self.features_dict)} 个特征文件\")\n",
    "        \n",
    "        return self.features_dict\n",
    "    \n",
    "    def get_feature_summary(self):\n",
    "        \"\"\"\n",
    "        获取所有特征文件的汇总信息\n",
    "        \n",
    "        返回:\n",
    "        - DataFrame: 汇总表\n",
    "        \"\"\"\n",
    "        if not self.feature_info:\n",
    "            print(\"请先加载特征文件\")\n",
    "            return None\n",
    "        \n",
    "        summary_data = []\n",
    "        for name, info in self.feature_info.items():\n",
    "            summary_data.append({\n",
    "                '特征文件': name,\n",
    "                '样本数': info['shape'][0],\n",
    "                '特征数': info['shape'][1] - 1 if 'CUST_NO' in info['columns'] else info['shape'][1],\n",
    "                '文件大小(MB)': round(info['file_size_mb'], 2),\n",
    "                '内存占用(MB)': round(info['memory_mb'], 2)\n",
    "            })\n",
    "        \n",
    "        summary_df = pd.DataFrame(summary_data)\n",
    "        summary_df = summary_df.sort_values('特征数', ascending=False).reset_index(drop=True)\n",
    "        \n",
    "        # 添加汇总行\n",
    "        total_row = pd.DataFrame([{\n",
    "            '特征文件': '总计',\n",
    "            '样本数': '-',\n",
    "            '特征数': summary_df['特征数'].sum(),\n",
    "            '文件大小(MB)': round(summary_df['文件大小(MB)'].sum(), 2),\n",
    "            '内存占用(MB)': round(summary_df['内存占用(MB)'].sum(), 2)\n",
    "        }])\n",
    "        \n",
    "        summary_df = pd.concat([summary_df, total_row], ignore_index=True)\n",
    "        \n",
    "        return summary_df"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "0ab626ec",
   "metadata": {},
   "source": [
    "### 加载训练集特征文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "c530b6ca",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "特征加载器已初始化\n",
      "发现 10 个特征文件\n",
      "================================================================================\n",
      "\n",
      "正在加载: TRAIN_AGET_PAY_features.pkl\n",
      "  - 形状: (4400, 47)\n",
      "  - 文件大小: 1.69 MB\n",
      "  - 内存占用: 1.92 MB\n",
      "\n",
      "正在加载: TRAIN_ASSET_features.pkl\n",
      "  - 形状: (48417, 139)\n",
      "  - 文件大小: 51.50 MB\n",
      "  - 内存占用: 53.98 MB\n",
      "\n",
      "正在加载: TRAIN_CCD_TR_DTL_features.pkl\n",
      "  - 形状: (169, 48)\n",
      "  - 文件大小: 0.07 MB\n",
      "  - 内存占用: 0.08 MB\n",
      "\n",
      "正在加载: TRAIN_MB_PAGEVIEW_DTL_features.pkl\n",
      "  - 形状: (24754, 503)\n",
      "  - 文件大小: 95.09 MB\n",
      "  - 内存占用: 96.34 MB\n",
      "\n",
      "正在加载: TRAIN_MB_TRNFLW_QRYTRNFLW_features.pkl\n",
      "  - 形状: (30002, 239)\n",
      "  - 文件大小: 55.26 MB\n",
      "  - 内存占用: 56.80 MB\n",
      "\n",
      "正在加载: TRAIN_NATURE_features.pkl\n",
      "  - 形状: (51397, 26)\n",
      "  - 文件大小: 10.35 MB\n",
      "  - 内存占用: 12.99 MB\n",
      "\n",
      "正在加载: TRAIN_PROD_HOLD_features.pkl\n",
      "  - 形状: (49448, 41)\n",
      "  - 文件大小: 14.29 MB\n",
      "  - 内存占用: 16.84 MB\n",
      "\n",
      "正在加载: TRAIN_TR_APS_DTL_features.pkl\n",
      "  - 形状: (48608, 309)\n",
      "  - 文件大小: 115.11 MB\n",
      "  - 内存占用: 117.61 MB\n",
      "\n",
      "正在加载: TRAIN_TR_IBTF_features.pkl\n",
      "  - 形状: (26375, 86)\n",
      "  - 文件大小: 17.29 MB\n",
      "  - 内存占用: 18.64 MB\n",
      "\n",
      "正在加载: TRAIN_TR_TPAY_features.pkl\n",
      "  - 形状: (30926, 65)\n",
      "  - 文件大小: 15.08 MB\n",
      "  - 内存占用: 16.66 MB\n",
      "\n",
      "================================================================================\n",
      "成功加载 10 个特征文件\n",
      "\n",
      "特征文件汇总:\n",
      "                                  特征文件    样本数   特征数  文件大小(MB)  内存占用(MB)\n",
      "0       TRAIN_MB_PAGEVIEW_DTL_features  24754   502     95.09     96.34\n",
      "1            TRAIN_TR_APS_DTL_features  48608   308    115.11    117.61\n",
      "2   TRAIN_MB_TRNFLW_QRYTRNFLW_features  30002   238     55.26     56.80\n",
      "3                 TRAIN_ASSET_features  48417   138     51.50     53.98\n",
      "4               TRAIN_TR_IBTF_features  26375    85     17.29     18.64\n",
      "5               TRAIN_TR_TPAY_features  30926    64     15.08     16.66\n",
      "6            TRAIN_CCD_TR_DTL_features    169    47      0.07      0.08\n",
      "7              TRAIN_AGET_PAY_features   4400    46      1.69      1.92\n",
      "8             TRAIN_PROD_HOLD_features  49448    40     14.29     16.84\n",
      "9                TRAIN_NATURE_features  51397    25     10.35     12.99\n",
      "10                                  总计      -  1493    375.73    391.86\n"
     ]
    }
   ],
   "source": [
    "# 创建加载器实例\n",
    "train_loader = FeatureLoader(feature_dir='./feature/Train')\n",
    "print(\"特征加载器已初始化\")\n",
    "\n",
    "# 加载所有特征文件\n",
    "train_features_dict = train_loader.load_all_features()\n",
    "\n",
    "# 查看特征文件汇总\n",
    "train_summary_df = train_loader.get_feature_summary()\n",
    "print(\"\\n特征文件汇总:\")\n",
    "print(train_summary_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "333e5a95",
   "metadata": {},
   "source": [
    "### 加载测试集特征文件"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "ed5acaf8",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "特征加载器已初始化\n",
      "发现 10 个特征文件\n",
      "================================================================================\n",
      "\n",
      "正在加载: A_AGET_PAY_features.pkl\n",
      "  - 形状: (530, 47)\n",
      "  - 文件大小: 0.21 MB\n",
      "  - 内存占用: 0.23 MB\n",
      "\n",
      "正在加载: A_ASSET_features.pkl\n",
      "  - 形状: (5624, 139)\n",
      "  - 文件大小: 5.99 MB\n",
      "  - 内存占用: 6.27 MB\n",
      "\n",
      "正在加载: A_CCD_TR_DTL_features.pkl\n",
      "  - 形状: (18, 48)\n",
      "  - 文件大小: 0.01 MB\n",
      "  - 内存占用: 0.01 MB\n",
      "\n",
      "正在加载: A_MB_PAGEVIEW_DTL_features.pkl\n",
      "  - 形状: (2753, 503)\n",
      "  - 文件大小: 10.60 MB\n",
      "  - 内存占用: 10.71 MB\n",
      "\n",
      "正在加载: A_MB_TRNFLW_QRYTRNFLW_features.pkl\n",
      "  - 形状: (3128, 239)\n",
      "  - 文件大小: 5.77 MB\n",
      "  - 内存占用: 5.92 MB\n",
      "\n",
      "正在加载: A_NATURE_features.pkl\n",
      "  - 形状: (5975, 26)\n",
      "  - 文件大小: 1.21 MB\n",
      "  - 内存占用: 1.51 MB\n",
      "\n",
      "正在加载: A_PROD_HOLD_features.pkl\n",
      "  - 形状: (5741, 41)\n",
      "  - 文件大小: 1.66 MB\n",
      "  - 内存占用: 1.95 MB\n",
      "\n",
      "正在加载: A_TR_APS_DTL_features.pkl\n",
      "  - 形状: (5616, 309)\n",
      "  - 文件大小: 13.29 MB\n",
      "  - 内存占用: 13.57 MB\n",
      "\n",
      "正在加载: A_TR_IBTF_features.pkl\n",
      "  - 形状: (2981, 86)\n",
      "  - 文件大小: 1.96 MB\n",
      "  - 内存占用: 2.11 MB\n",
      "\n",
      "正在加载: A_TR_TPAY_features.pkl\n",
      "  - 形状: (3595, 65)\n",
      "  - 文件大小: 1.76 MB\n",
      "  - 内存占用: 1.94 MB\n",
      "\n",
      "================================================================================\n",
      "成功加载 10 个特征文件\n",
      "\n",
      "特征文件汇总:\n",
      "                              特征文件   样本数   特征数  文件大小(MB)  内存占用(MB)\n",
      "0       A_MB_PAGEVIEW_DTL_features  2753   502     10.60     10.71\n",
      "1            A_TR_APS_DTL_features  5616   308     13.29     13.57\n",
      "2   A_MB_TRNFLW_QRYTRNFLW_features  3128   238      5.77      5.92\n",
      "3                 A_ASSET_features  5624   138      5.99      6.27\n",
      "4               A_TR_IBTF_features  2981    85      1.96      2.11\n",
      "5               A_TR_TPAY_features  3595    64      1.76      1.94\n",
      "6            A_CCD_TR_DTL_features    18    47      0.01      0.01\n",
      "7              A_AGET_PAY_features   530    46      0.21      0.23\n",
      "8             A_PROD_HOLD_features  5741    40      1.66      1.95\n",
      "9                A_NATURE_features  5975    25      1.21      1.51\n",
      "10                              总计     -  1493     42.46     44.22\n"
     ]
    }
   ],
   "source": [
    "# 创建加载器实例\n",
    "test_loader = FeatureLoader(feature_dir='./feature/A')\n",
    "print(\"特征加载器已初始化\")\n",
    "\n",
    "# 加载所有特征文件\n",
    "test_features_dict = test_loader.load_all_features()\n",
    "\n",
    "# 查看特征文件汇总\n",
    "test_summary_df = test_loader.get_feature_summary()\n",
    "print(\"\\n特征文件汇总:\")\n",
    "print(test_summary_df)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "62051560",
   "metadata": {},
   "source": [
    "# 建模训练"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "9d2e70a4",
   "metadata": {},
   "source": [
    "# 问题诊断与改进方案分析\n",
    "\n",
    "## 一、核心问题诊断\n",
    "\n",
    "根据当前模型表现：\n",
    "- 训练集分数：Macro-F1: 0.606428\n",
    "- 验证集分数：Macro-F1: 0.442329\n",
    "- 差异：0.164099 (过拟合)\n",
    "\n",
    "### 关键问题识别\n",
    "\n",
    "#### 1. 严重过拟合问题\n",
    "差异达到16.4%，说明模型在训练集上学到的模式无法很好泛化到验证集：\n",
    "- 模型复杂度过高\n",
    "- 训练和验证集分布不一致\n",
    "- 可能存在数据泄露\n",
    "\n",
    "#### 2. 特征工程问题分析\n",
    "\n",
    "通过回顾各特征工程文件，发现以下问题：\n",
    "\n",
    "**2.1 活期交易明细(2-1)**\n",
    "- 创建了大量统计特征（count, sum, mean, std等）\n",
    "- 可能存在信息冗余\n",
    "- 缺少时序特征和趋势特征\n",
    "\n",
    "**2.2 掌银页面访问(2-2)**\n",
    "- 使用了Word2Vec做文本特征提取\n",
    "- TF-IDF和SVD降维\n",
    "- 可能过于复杂，引入噪声\n",
    "\n",
    "**2.3 资产/产品/自然表(2-3)**\n",
    "- 多表特征叠加\n",
    "- 特征数量巨大\n",
    "- 可能存在大量低质量特征\n",
    "\n",
    "**2.4 掌银交易(2-4)**\n",
    "- 金融和非金融交易混合\n",
    "- 类别特征编码可能不合理\n",
    "\n",
    "**2.5 代发工资/信用卡(2-5)**\n",
    "- 创建了大量派生特征\n",
    "- 部分特征可能相关性过高\n",
    "\n",
    "#### 3. 建模策略问题\n",
    "\n",
    "从3-1-1文件分析：\n",
    "- 使用了Autogluon的best_quality preset\n",
    "- 设置了2层Stacking + 2组Bagging\n",
    "- 模型复杂度极高，容易过拟合\n",
    "- 未进行特征选择\n",
    "- 未充分利用时间维度信息\n",
    "\n",
    "#### 4. 数据质量问题\n",
    "\n",
    "- 训练集和测试集可能分布不一致\n",
    "- 缺失值处理可能不当\n",
    "- 可能存在常量特征和低方差特征\n",
    "\n",
    "## 二、改进方案设计\n",
    "\n",
    "### 方案1：特征优化与降维（优先级：高）\n",
    "\n",
    "**目标**：减少特征维度，提升特征质量\n",
    "\n",
    "**具体措施**：\n",
    "1. 特征选择\n",
    "   - 使用特征重要性筛选Top-N特征\n",
    "   - 移除低方差特征（方差<0.01）\n",
    "   - 移除高缺失率特征（>80%）\n",
    "   - 移除常量特征\n",
    "\n",
    "2. 特征去冗余\n",
    "   - 计算特征相关性矩阵\n",
    "   - 移除高度相关特征（相关系数>0.95）\n",
    "   - 保留最重要的代表性特征\n",
    "\n",
    "3. 特征工程优化\n",
    "   - 增加时间序列特征（滑动窗口统计）\n",
    "   - 增加交叉特征（关键特征组合）\n",
    "   - 优化类别特征编码（Target Encoding with smoothing）\n",
    "\n",
    "### 方案2：模型简化与正则化（优先级：高）\n",
    "\n",
    "**目标**：降低模型复杂度，提升泛化能力\n",
    "\n",
    "**具体措施**：\n",
    "1. 调整Autogluon配置\n",
    "   - 从best_quality改为medium_quality\n",
    "   - 减少Stacking层数（2层→1层）\n",
    "   - 减少Bagging组数（2组→1组）\n",
    "   - 缩短训练时间\n",
    "\n",
    "2. 增强正则化\n",
    "   - 提高L1/L2正则化系数\n",
    "   - 降低学习率\n",
    "   - 增加Dropout概率\n",
    "   - 使用Early Stopping\n",
    "\n",
    "3. 超参数优化\n",
    "   - 减少树的深度\n",
    "   - 增加min_child_samples\n",
    "   - 降低feature_fraction\n",
    "\n",
    "### 方案3：数据增强与验证策略（优先级：中）\n",
    "\n",
    "**目标**：确保训练和验证集分布一致\n",
    "\n",
    "**具体措施**：\n",
    "1. 时间感知的交叉验证\n",
    "   - 使用TimeSeriesSplit替代StratifiedKFold\n",
    "   - 确保验证集时间晚于训练集\n",
    "\n",
    "2. 分层采样优化\n",
    "   - 在类别不平衡时使用SMOTE\n",
    "   - 或调整class_weight参数\n",
    "\n",
    "3. 数据清洗\n",
    "   - 检测并处理异常值\n",
    "   - 标准化数值特征\n",
    "   - 统一缺失值填充策略\n",
    "\n",
    "### 方案4：集成学习优化（优先级：中）\n",
    "\n",
    "**目标**：在控制复杂度前提下提升性能\n",
    "\n",
    "**具体措施**：\n",
    "1. 多样性模型组合\n",
    "   - 保留LightGBM、XGBoost、CatBoost\n",
    "   - 移除神经网络模型（易过拟合）\n",
    "   - 简化模型超参数搜索空间\n",
    "\n",
    "2. 加权集成\n",
    "   - 根据验证集表现动态调整权重\n",
    "   - 使用Blending而非Stacking\n",
    "\n",
    "## 三、实施优先级\n",
    "\n",
    "### 第一阶段（立即实施）：\n",
    "1. 特征选择与去冗余\n",
    "2. 模型简化（改为medium_quality）\n",
    "3. 减少Stacking/Bagging层数\n",
    "\n",
    "预期效果：验证集Macro-F1提升至0.50+，过拟合降至10%以内\n",
    "\n",
    "### 第二阶段（后续优化）：\n",
    "1. 时间特征工程优化\n",
    "2. 交叉特征创建\n",
    "3. 验证策略改进\n",
    "\n",
    "预期效果：验证集Macro-F1提升至0.53+\n",
    "\n",
    "### 第三阶段（精细调优）：\n",
    "1. 超参数网格搜索\n",
    "2. 模型集成优化\n",
    "3. 阈值调优\n",
    "\n",
    "预期效果：验证集Macro-F1提升至0.55+\n",
    "\n",
    "## 四、关键监控指标\n",
    "\n",
    "1. 训练集与验证集差异（<10%为佳）\n",
    "2. 特征数量（建议控制在200-500）\n",
    "3. 各类别F1分数（关注最小类别）\n",
    "4. 训练时间（控制在1小时内）"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "eb5c5055",
   "metadata": {},
   "source": [
    "# 方案1实施：特征优化与降维"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5c17dfc4",
   "metadata": {},
   "source": [
    "## 步骤1：数据合并"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "4fc4d40e",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤1: 合并训练集和测试集特征\n",
      "====================================================================================================\n",
      "\n",
      "初始训练集形状: (51397, 3)\n",
      "合并特征: TRAIN_AGET_PAY_features, 形状: (4400, 47)\n",
      "合并特征: TRAIN_ASSET_features, 形状: (48417, 139)\n",
      "合并特征: TRAIN_CCD_TR_DTL_features, 形状: (169, 48)\n",
      "合并特征: TRAIN_MB_PAGEVIEW_DTL_features, 形状: (24754, 503)\n",
      "合并特征: TRAIN_MB_TRNFLW_QRYTRNFLW_features, 形状: (30002, 239)\n",
      "合并特征: TRAIN_NATURE_features, 形状: (51397, 26)\n",
      "合并特征: TRAIN_PROD_HOLD_features, 形状: (49448, 41)\n",
      "合并特征: TRAIN_TR_APS_DTL_features, 形状: (48608, 309)\n",
      "合并特征: TRAIN_TR_IBTF_features, 形状: (26375, 86)\n",
      "合并特征: TRAIN_TR_TPAY_features, 形状: (30926, 65)\n",
      "训练集合并后形状: (51397, 1496)\n",
      "训练集特征数: 1494 (不含CUST_NO和FLAG)\n",
      "\n",
      "初始测试集形状: (5975, 2)\n",
      "合并特征: A_AGET_PAY_features, 形状: (530, 47)\n",
      "合并特征: A_ASSET_features, 形状: (5624, 139)\n",
      "合并特征: A_CCD_TR_DTL_features, 形状: (18, 48)\n",
      "合并特征: A_MB_PAGEVIEW_DTL_features, 形状: (2753, 503)\n",
      "合并特征: A_MB_TRNFLW_QRYTRNFLW_features, 形状: (3128, 239)\n",
      "合并特征: A_NATURE_features, 形状: (5975, 26)\n",
      "合并特征: A_PROD_HOLD_features, 形状: (5741, 41)\n",
      "合并特征: A_TR_APS_DTL_features, 形状: (5616, 309)\n",
      "合并特征: A_TR_IBTF_features, 形状: (2981, 86)\n",
      "合并特征: A_TR_TPAY_features, 形状: (3595, 65)\n",
      "测试集合并后形状: (5975, 1495)\n",
      "测试集特征数: 1494 (不含CUST_NO)\n",
      "\n",
      "对齐后:\n",
      "训练集形状: (51397, 1469)\n",
      "测试集形状: (5975, 1468)\n",
      "共同特征数: 1467\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤1: 合并训练集和测试集特征\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 合并训练集特征\n",
    "train_merged = train_target_cust.copy()\n",
    "print(f\"\\n初始训练集形状: {train_merged.shape}\")\n",
    "\n",
    "for feature_name, feature_df in train_features_dict.items():\n",
    "    print(f\"合并特征: {feature_name}, 形状: {feature_df.shape}\")\n",
    "    train_merged = train_merged.merge(feature_df, on='CUST_NO', how='left')\n",
    "\n",
    "print(f\"训练集合并后形状: {train_merged.shape}\")\n",
    "print(f\"训练集特征数: {train_merged.shape[1] - 2} (不含CUST_NO和FLAG)\")\n",
    "\n",
    "# 合并测试集特征\n",
    "test_merged = test_target_cust.copy()\n",
    "print(f\"\\n初始测试集形状: {test_merged.shape}\")\n",
    "\n",
    "for feature_name, feature_df in test_features_dict.items():\n",
    "    print(f\"合并特征: {feature_name}, 形状: {feature_df.shape}\")\n",
    "    test_merged = test_merged.merge(feature_df, on='CUST_NO', how='left')\n",
    "\n",
    "print(f\"测试集合并后形状: {test_merged.shape}\")\n",
    "print(f\"测试集特征数: {test_merged.shape[1] - 1} (不含CUST_NO)\")\n",
    "\n",
    "# 确保训练集和测试集列一致\n",
    "train_cols = set(train_merged.columns) - {'FLAG'}\n",
    "test_cols = set(test_merged.columns)\n",
    "common_cols = ['CUST_NO'] + sorted(list(train_cols & test_cols - {'CUST_NO'}))\n",
    "\n",
    "train_merged = train_merged[common_cols + ['FLAG']]\n",
    "test_merged = test_merged[common_cols]\n",
    "\n",
    "print(f\"\\n对齐后:\")\n",
    "print(f\"训练集形状: {train_merged.shape}\")\n",
    "print(f\"测试集形状: {test_merged.shape}\")\n",
    "print(f\"共同特征数: {len(common_cols) - 1}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7950e3f7",
   "metadata": {},
   "source": [
    "## 步骤2：特征质量检查与清洗"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "3ee949e5",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤2: 特征质量检查与清洗\n",
      "====================================================================================================\n",
      "\n",
      "原始特征数: 1467\n",
      "\n",
      "1. 检查常量特征...\n",
      "   发现 44 个常量特征，将被移除\n",
      "\n",
      "2. 检查高缺失率特征...\n",
      "   发现 499 个高缺失率特征（>80.0%），将被移除\n",
      "     - active_days_x: 99.67%\n",
      "     - active_months_x: 91.44%\n",
      "     - active_months_y: 99.67%\n",
      "     - aget_day_diff_cv: 93.04%\n",
      "     - aget_day_diff_max: 92.09%\n",
      "     - aget_day_diff_mean: 92.09%\n",
      "     - aget_day_diff_median: 92.09%\n",
      "     - aget_day_diff_min: 92.09%\n",
      "     - aget_day_diff_range: 92.09%\n",
      "     - aget_day_diff_std: 93.04%\n",
      "     ... 还有 489 个\n",
      "\n",
      "3. 检查低方差特征...\n",
      "   发现 10 个低方差特征（<0.01），将被移除\n",
      "     - TPAY_MOTH_IS_NET_IN: var=0.002837\n",
      "     - TPAY_SEAN_IS_NET_IN: var=0.002162\n",
      "     - aps_outlier_ratio_30d: var=0.000331\n",
      "     - aps_outlier_ratio_60d: var=0.000351\n",
      "     - aps_outlier_ratio_90d: var=0.000377\n",
      "     - mb_trnflw_activity_proportion: var=0.007656\n",
      "     - mb_trnflw_qrytrnflw_count_ratio_last_14d: var=0.008360\n",
      "     - mb_trnflw_qrytrnflw_count_ratio_last_7d: var=0.005739\n",
      "     - mb_trnflw_zero_amount_ratio: var=0.000593\n",
      "     - same_page_consecutive_ratio: var=0.007509\n",
      "\n",
      "清洗后特征数: 914\n",
      "移除特征数: 553\n",
      "\n",
      "4. 缺失值统计:\n",
      "   有缺失值的特征数: 889\n",
      "   总缺失率: 38.04%\n",
      "\n",
      "   缺失最严重的前10个特征:\n",
      "     - aps_in_out_freq_diff_30d: 41018 (79.81%)\n",
      "     - aps_in_out_amt_ratio_30d: 41018 (79.81%)\n",
      "     - aps_net_inflow_30d: 41018 (79.81%)\n",
      "     - aps_in_out_cnt_ratio_30d: 41018 (79.81%)\n",
      "     - page_c5b386b7a6348a2f1ba70f2259fb827e_count: 40905 (79.59%)\n",
      "     - page_c5b386b7a6348a2f1ba70f2259fb827e_last_visit: 40905 (79.59%)\n",
      "     - page_c5b386b7a6348a2f1ba70f2259fb827e_visit_days: 40905 (79.59%)\n",
      "     - aps_top3_chl_cnt_60d: 40887 (79.55%)\n",
      "     - week_5_count: 40687 (79.16%)\n",
      "     - page_path_count_796f39eb9f0d6e6ff63d327d96fedeb5_dc127d306179477fef4f3a9378dc550b: 40606 (79.00%)\n",
      "\n",
      "5. 数据类型分布:\n",
      "   float64: 904\n",
      "   int32: 6\n",
      "   int64: 4\n",
      "\n",
      "6. 训练集标签分布:\n",
      "   类别 1:   1459 ( 2.84%)\n",
      "   类别 2:    309 ( 0.60%)\n",
      "   类别 3:   5586 (10.87%)\n",
      "   类别 4:  15303 (29.77%)\n",
      "   类别 5:   8193 (15.94%)\n",
      "   类别 6:  10261 (19.96%)\n",
      "   类别 7:   1276 ( 2.48%)\n",
      "   类别 8:   2498 ( 4.86%)\n",
      "   类别 9:   6476 (12.60%)\n",
      "   类别 10:     36 ( 0.07%)\n",
      "\n",
      "   类别不平衡比: 425.08:1\n",
      "   警告: 存在严重类别不平衡!\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤2: 特征质量检查与清洗\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 准备特征数据（去除ID和标签）\n",
    "X_train_raw = train_merged.drop(columns=['CUST_NO', 'FLAG'])\n",
    "y_train = train_merged['FLAG']\n",
    "X_test_raw = test_merged.drop(columns=['CUST_NO'])\n",
    "\n",
    "print(f\"\\n原始特征数: {X_train_raw.shape[1]}\")\n",
    "\n",
    "# 1. 移除常量特征\n",
    "print(\"\\n1. 检查常量特征...\")\n",
    "constant_features = []\n",
    "for col in X_train_raw.columns:\n",
    "    if X_train_raw[col].nunique() <= 1:\n",
    "        constant_features.append(col)\n",
    "\n",
    "if constant_features:\n",
    "    print(f\"   发现 {len(constant_features)} 个常量特征，将被移除\")\n",
    "    X_train_raw = X_train_raw.drop(columns=constant_features)\n",
    "    X_test_raw = X_test_raw.drop(columns=constant_features)\n",
    "else:\n",
    "    print(\"   未发现常量特征\")\n",
    "\n",
    "# 2. 移除高缺失率特征（>80%）\n",
    "print(\"\\n2. 检查高缺失率特征...\")\n",
    "missing_threshold = 0.8\n",
    "high_missing_features = []\n",
    "missing_rates = X_train_raw.isnull().mean()\n",
    "high_missing_features = missing_rates[missing_rates > missing_threshold].index.tolist()\n",
    "\n",
    "if high_missing_features:\n",
    "    print(f\"   发现 {len(high_missing_features)} 个高缺失率特征（>{missing_threshold*100}%），将被移除\")\n",
    "    for feat in high_missing_features[:10]:\n",
    "        print(f\"     - {feat}: {missing_rates[feat]*100:.2f}%\")\n",
    "    if len(high_missing_features) > 10:\n",
    "        print(f\"     ... 还有 {len(high_missing_features)-10} 个\")\n",
    "    X_train_raw = X_train_raw.drop(columns=high_missing_features)\n",
    "    X_test_raw = X_test_raw.drop(columns=high_missing_features)\n",
    "else:\n",
    "    print(f\"   未发现高缺失率特征\")\n",
    "\n",
    "# 3. 移除低方差特征\n",
    "print(\"\\n3. 检查低方差特征...\")\n",
    "variance_threshold = 0.01\n",
    "low_var_features = []\n",
    "\n",
    "for col in X_train_raw.select_dtypes(include=[np.number]).columns:\n",
    "    if X_train_raw[col].var() < variance_threshold:\n",
    "        low_var_features.append(col)\n",
    "\n",
    "if low_var_features:\n",
    "    print(f\"   发现 {len(low_var_features)} 个低方差特征（<{variance_threshold}），将被移除\")\n",
    "    for feat in low_var_features[:10]:\n",
    "        print(f\"     - {feat}: var={X_train_raw[feat].var():.6f}\")\n",
    "    if len(low_var_features) > 10:\n",
    "        print(f\"     ... 还有 {len(low_var_features)-10} 个\")\n",
    "    X_train_raw = X_train_raw.drop(columns=low_var_features)\n",
    "    X_test_raw = X_test_raw.drop(columns=low_var_features)\n",
    "else:\n",
    "    print(\"   未发现低方差特征\")\n",
    "\n",
    "print(f\"\\n清洗后特征数: {X_train_raw.shape[1]}\")\n",
    "print(f\"移除特征数: {len(constant_features) + len(high_missing_features) + len(low_var_features)}\")\n",
    "\n",
    "# 4. 缺失值统计\n",
    "print(\"\\n4. 缺失值统计:\")\n",
    "train_missing = X_train_raw.isnull().sum()\n",
    "train_missing_cols = train_missing[train_missing > 0].sort_values(ascending=False)\n",
    "print(f\"   有缺失值的特征数: {len(train_missing_cols)}\")\n",
    "print(f\"   总缺失率: {X_train_raw.isnull().sum().sum() / (X_train_raw.shape[0] * X_train_raw.shape[1]) * 100:.2f}%\")\n",
    "\n",
    "if len(train_missing_cols) > 0:\n",
    "    print(f\"\\n   缺失最严重的前10个特征:\")\n",
    "    for feat, count in train_missing_cols.head(10).items():\n",
    "        rate = count / len(X_train_raw) * 100\n",
    "        print(f\"     - {feat}: {count} ({rate:.2f}%)\")\n",
    "\n",
    "# 5. 数据类型统计\n",
    "print(\"\\n5. 数据类型分布:\")\n",
    "dtype_counts = X_train_raw.dtypes.value_counts()\n",
    "for dtype, count in dtype_counts.items():\n",
    "    print(f\"   {dtype}: {count}\")\n",
    "\n",
    "# 6. 标签分布\n",
    "print(\"\\n6. 训练集标签分布:\")\n",
    "label_dist = y_train.value_counts().sort_index()\n",
    "for label, count in label_dist.items():\n",
    "    rate = count / len(y_train) * 100\n",
    "    print(f\"   类别 {label}: {count:6d} ({rate:5.2f}%)\")\n",
    "\n",
    "# 计算类别不平衡比\n",
    "max_count = label_dist.max()\n",
    "min_count = label_dist.min()\n",
    "imbalance_ratio = max_count / min_count\n",
    "print(f\"\\n   类别不平衡比: {imbalance_ratio:.2f}:1\")\n",
    "if imbalance_ratio > 5:\n",
    "    print(\"   警告: 存在严重类别不平衡!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c733eda3",
   "metadata": {},
   "source": [
    "## 步骤3：特征去冗余（移除高相关特征）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "3d2416de",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤3: 特征去冗余 - 移除高相关特征\n",
      "====================================================================================================\n",
      "\n",
      "数值型特征数: 914\n",
      "\n",
      "计算特征相关性矩阵（可能需要几分钟）...\n",
      "\n",
      "查找相关系数>0.95的特征对...\n",
      "\n",
      "发现 218 个高相关特征，将被移除\n",
      "  - unique_model_paths\n",
      "  - ASSET_YAVER_TD_DIV_AUM\n",
      "  - ASSET_SAVER_TD_DIV_AUM\n",
      "  - aps_in_60d_sum\n",
      "  - aps_txn_count_15d\n",
      "  - mb_trnflw_amount_max_last_90d\n",
      "  - total_referrer_nunique\n",
      "  - ASSET_MAVER_TD_DIV_AUM\n",
      "  - aps_top3_chl_cnt_90d\n",
      "  - daily_visits_min\n",
      "  - month_2_visit_count\n",
      "  - mb_trnflw_amount_mean_last_1d\n",
      "  - aps_all_90d_sum\n",
      "  - aps_in_90d_count\n",
      "  - unique_page_paths\n",
      "  - IBTF_MOTH_DIV_SEAN_CNT\n",
      "  - IBTF_TR_AMT_NET\n",
      "  - AST_SAVER_DPSA_BAL\n",
      "  - mb_trnflw_qrytrnflw_count_diff_last_90d\n",
      "  - session_visit_std\n",
      "  ... 还有 198 个\n",
      "\n",
      "去冗余后特征数: 696\n",
      "移除高相关特征数: 218\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤3: 特征去冗余 - 移除高相关特征\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 只对数值型特征计算相关性\n",
    "numeric_cols = X_train_raw.select_dtypes(include=[np.number]).columns.tolist()\n",
    "print(f\"\\n数值型特征数: {len(numeric_cols)}\")\n",
    "\n",
    "if len(numeric_cols) > 1:\n",
    "    print(\"\\n计算特征相关性矩阵（可能需要几分钟）...\")\n",
    "    \n",
    "    # 填充缺失值（使用中位数）\n",
    "    X_train_numeric = X_train_raw[numeric_cols].fillna(X_train_raw[numeric_cols].median())\n",
    "    \n",
    "    # 计算相关性矩阵\n",
    "    corr_matrix = X_train_numeric.corr().abs()\n",
    "    \n",
    "    # 找出高相关特征对\n",
    "    correlation_threshold = 0.95\n",
    "    high_corr_features = set()\n",
    "    \n",
    "    print(f\"\\n查找相关系数>{correlation_threshold}的特征对...\")\n",
    "    \n",
    "    for i in range(len(corr_matrix.columns)):\n",
    "        for j in range(i+1, len(corr_matrix.columns)):\n",
    "            if corr_matrix.iloc[i, j] > correlation_threshold:\n",
    "                col_i = corr_matrix.columns[i]\n",
    "                col_j = corr_matrix.columns[j]\n",
    "                \n",
    "                # 保留其中一个特征，移除另一个\n",
    "                # 这里简单地保留第一个，移除第二个\n",
    "                high_corr_features.add(col_j)\n",
    "    \n",
    "    if high_corr_features:\n",
    "        print(f\"\\n发现 {len(high_corr_features)} 个高相关特征，将被移除\")\n",
    "        for feat in list(high_corr_features)[:20]:\n",
    "            print(f\"  - {feat}\")\n",
    "        if len(high_corr_features) > 20:\n",
    "            print(f\"  ... 还有 {len(high_corr_features)-20} 个\")\n",
    "        \n",
    "        # 移除高相关特征\n",
    "        X_train_raw = X_train_raw.drop(columns=list(high_corr_features))\n",
    "        X_test_raw = X_test_raw.drop(columns=list(high_corr_features))\n",
    "    else:\n",
    "        print(f\"\\n未发现相关系数>{correlation_threshold}的特征对\")\n",
    "else:\n",
    "    print(\"\\n数值型特征太少，跳过相关性分析\")\n",
    "    high_corr_features = set()\n",
    "\n",
    "print(f\"\\n去冗余后特征数: {X_train_raw.shape[1]}\")\n",
    "print(f\"移除高相关特征数: {len(high_corr_features)}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b002a5ed",
   "metadata": {},
   "source": [
    "## 步骤4：缺失值处理"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "83ccceda",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤4: 缺失值处理\n",
      "====================================================================================================\n",
      "\n",
      "缺失值处理策略:\n",
      "  - 数值型特征: 使用中位数填充\n",
      "  - 类别型特征: 使用'missing'标记\n",
      "\n",
      "处理前总缺失值数: 14686905\n",
      "\n",
      "处理 696 个数值型特征...\n",
      "\n",
      "处理后总缺失值数: 0\n",
      "填充缺失值数: 14686905\n",
      "\n",
      "缺失值处理完成!\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤4: 缺失值处理\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# Autogluon会自动处理缺失值，但我们可以做一些基础处理\n",
    "print(\"\\n缺失值处理策略:\")\n",
    "print(\"  - 数值型特征: 使用中位数填充\")\n",
    "print(\"  - 类别型特征: 使用'missing'标记\")\n",
    "\n",
    "# 统计处理前的缺失情况\n",
    "total_missing_before = X_train_raw.isnull().sum().sum()\n",
    "print(f\"\\n处理前总缺失值数: {total_missing_before}\")\n",
    "\n",
    "# 数值型特征 - 中位数填充\n",
    "numeric_cols = X_train_raw.select_dtypes(include=[np.number]).columns.tolist()\n",
    "if len(numeric_cols) > 0:\n",
    "    print(f\"\\n处理 {len(numeric_cols)} 个数值型特征...\")\n",
    "    for col in numeric_cols:\n",
    "        if X_train_raw[col].isnull().sum() > 0:\n",
    "            median_val = X_train_raw[col].median()\n",
    "            X_train_raw[col].fillna(median_val, inplace=True)\n",
    "            X_test_raw[col].fillna(median_val, inplace=True)\n",
    "\n",
    "# 类别型特征 - 使用'missing'标记\n",
    "categorical_cols = X_train_raw.select_dtypes(include=['object', 'category']).columns.tolist()\n",
    "if len(categorical_cols) > 0:\n",
    "    print(f\"处理 {len(categorical_cols)} 个类别型特征...\")\n",
    "    for col in categorical_cols:\n",
    "        if X_train_raw[col].isnull().sum() > 0:\n",
    "            X_train_raw[col].fillna('missing', inplace=True)\n",
    "            X_test_raw[col].fillna('missing', inplace=True)\n",
    "\n",
    "# 统计处理后的缺失情况\n",
    "total_missing_after = X_train_raw.isnull().sum().sum()\n",
    "print(f\"\\n处理后总缺失值数: {total_missing_after}\")\n",
    "print(f\"填充缺失值数: {total_missing_before - total_missing_after}\")\n",
    "\n",
    "print(\"\\n缺失值处理完成!\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d675e79b",
   "metadata": {},
   "source": [
    "# 方案2实施：模型简化与正则化"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ee09f20f",
   "metadata": {},
   "source": [
    "## 步骤5：准备训练数据"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "24670c9c",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤5: 准备训练数据\n",
      "====================================================================================================\n",
      "\n",
      "最终特征数: 696\n",
      "训练集样本数: 51397\n",
      "测试集样本数: 5975\n",
      "标签分布:\n",
      "  类别 1:   1459 ( 2.84%)\n",
      "  类别 2:    309 ( 0.60%)\n",
      "  类别 3:   5586 (10.87%)\n",
      "  类别 4:  15303 (29.77%)\n",
      "  类别 5:   8193 (15.94%)\n",
      "  类别 6:  10261 (19.96%)\n",
      "  类别 7:   1276 ( 2.48%)\n",
      "  类别 8:   2498 ( 4.86%)\n",
      "  类别 9:   6476 (12.60%)\n",
      "  类别 10:     36 ( 0.07%)\n",
      "\n",
      "转换为Autogluon TabularDataset格式...\n",
      "\n",
      "数据准备完成!\n",
      "  训练数据形状: (51397, 697)\n",
      "  测试数据形状: (5975, 696)\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤5: 准备训练数据\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 准备训练数据\n",
    "X_train = X_train_raw.copy()\n",
    "X_test = X_test_raw.copy()\n",
    "test_cust_no = test_merged['CUST_NO'].copy()\n",
    "\n",
    "print(f\"\\n最终特征数: {X_train.shape[1]}\")\n",
    "print(f\"训练集样本数: {X_train.shape[0]}\")\n",
    "print(f\"测试集样本数: {X_test.shape[0]}\")\n",
    "print(f\"标签分布:\")\n",
    "for label, count in y_train.value_counts().sort_index().items():\n",
    "    rate = count / len(y_train) * 100\n",
    "    print(f\"  类别 {label}: {count:6d} ({rate:5.2f}%)\")\n",
    "\n",
    "# 转换为TabularDataset\n",
    "print(f\"\\n转换为Autogluon TabularDataset格式...\")\n",
    "train_data_ag = TabularDataset(pd.concat([X_train, y_train], axis=1))\n",
    "test_data_ag = TabularDataset(X_test)\n",
    "\n",
    "print(\"\\n数据准备完成!\")\n",
    "print(f\"  训练数据形状: {train_data_ag.shape}\")\n",
    "print(f\"  测试数据形状: {test_data_ag.shape}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "815490d3",
   "metadata": {},
   "source": [
    "## 步骤6：定义自定义评分器"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "4226911a",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "自定义Macro-F1评分器已创建\n",
      "评分器名称: macro_f1\n",
      "最优值: 1.0\n"
     ]
    }
   ],
   "source": [
    "from sklearn.metrics import f1_score\n",
    "\n",
    "def macro_f1_score(y_true, y_pred):\n",
    "    \"\"\"自定义Macro-F1分数计算函数\"\"\"\n",
    "    return f1_score(y_true, y_pred, average='macro')\n",
    "\n",
    "# 创建Autogluon评分器\n",
    "macro_f1_scorer = make_scorer(\n",
    "    name='macro_f1',\n",
    "    score_func=macro_f1_score,\n",
    "    optimum=1.0,\n",
    "    greater_is_better=True,\n",
    "    needs_proba=False,\n",
    "    needs_threshold=False\n",
    ")\n",
    "\n",
    "print(\"自定义Macro-F1评分器已创建\")\n",
    "print(f\"评分器名称: {macro_f1_scorer.name}\")\n",
    "print(f\"最优值: {macro_f1_scorer.optimum}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "5f2d145b",
   "metadata": {},
   "source": [
    "## 步骤7：优化后的Autogluon模型训练（防过拟合版本）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "129cb6d1",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "Warning: path already exists! This predictor may overwrite an existing predictor! path=\"./model/autogluon_optimized\"\n",
      "Verbosity: 2 (Standard Logging)\n",
      "=================== System Info ===================\n",
      "AutoGluon Version:  1.3.1\n",
      "Python Version:     3.10.18\n",
      "Operating System:   Windows\n",
      "Platform Machine:   AMD64\n",
      "Platform Version:   10.0.26100\n",
      "CPU Count:          32\n",
      "Memory Avail:       97.55 GB / 127.82 GB (76.3%)\n",
      "Disk Space Avail:   1321.38 GB / 3815.38 GB (34.6%)\n",
      "===================================================\n",
      "Presets specified: ['medium_quality']\n",
      "Beginning AutoGluon training ... Time limit = 3600s\n",
      "AutoGluon will save models to \"e:\\DevWork\\6-Model\\star-cup2025\\Model\\model\\autogluon_optimized\"\n",
      "Train Data Rows:    51397\n",
      "Train Data Columns: 696\n",
      "Label Column:       FLAG\n",
      "Problem Type:       multiclass\n",
      "Preprocessing data ...\n",
      "Verbosity: 2 (Standard Logging)\n",
      "=================== System Info ===================\n",
      "AutoGluon Version:  1.3.1\n",
      "Python Version:     3.10.18\n",
      "Operating System:   Windows\n",
      "Platform Machine:   AMD64\n",
      "Platform Version:   10.0.26100\n",
      "CPU Count:          32\n",
      "Memory Avail:       97.55 GB / 127.82 GB (76.3%)\n",
      "Disk Space Avail:   1321.38 GB / 3815.38 GB (34.6%)\n",
      "===================================================\n",
      "Presets specified: ['medium_quality']\n",
      "Beginning AutoGluon training ... Time limit = 3600s\n",
      "AutoGluon will save models to \"e:\\DevWork\\6-Model\\star-cup2025\\Model\\model\\autogluon_optimized\"\n",
      "Train Data Rows:    51397\n",
      "Train Data Columns: 696\n",
      "Label Column:       FLAG\n",
      "Problem Type:       multiclass\n",
      "Preprocessing data ...\n",
      "Train Data Class Count: 10\n",
      "Using Feature Generators to preprocess the data ...\n",
      "Fitting AutoMLPipelineFeatureGenerator...\n",
      "Train Data Class Count: 10\n",
      "Using Feature Generators to preprocess the data ...\n",
      "Fitting AutoMLPipelineFeatureGenerator...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤7: 优化后的Autogluon模型训练（防过拟合版本）\n",
      "====================================================================================================\n",
      "\n",
      "模型输出目录: ./model/autogluon_optimized\n",
      "\n",
      "优化策略:\n",
      "  1. 降低模型复杂度：medium_quality preset\n",
      "  2. 减少集成层数：1层Stacking + 1组Bagging\n",
      "  3. 增强正则化：更高的L1/L2系数，更深的树限制\n",
      "  4. 排除易过拟合模型：KNN、神经网络\n",
      "  5. 更保守的超参数设置\n",
      "\n",
      "预计训练时间: 45-60分钟\n",
      "====================================================================================================\n",
      "\n",
      "开始训练...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\tAvailable Memory:                    99881.00 MB\n",
      "\tTrain Data (Original)  Memory Usage: 271.94 MB (0.3% of available memory)\n",
      "\tTrain Data (Original)  Memory Usage: 271.94 MB (0.3% of available memory)\n",
      "\tInferring data type of each feature based on column values. Set feature_metadata_in to manually specify special dtypes of the features.\n",
      "\tInferring data type of each feature based on column values. Set feature_metadata_in to manually specify special dtypes of the features.\n",
      "\tStage 1 Generators:\n",
      "\t\tFitting AsTypeFeatureGenerator...\n",
      "\tStage 1 Generators:\n",
      "\t\tFitting AsTypeFeatureGenerator...\n",
      "\t\t\tNote: Converting 35 features to boolean dtype as they only contain 2 unique values.\n",
      "\t\t\tNote: Converting 35 features to boolean dtype as they only contain 2 unique values.\n",
      "\tStage 2 Generators:\n",
      "\t\tFitting FillNaFeatureGenerator...\n",
      "\tStage 2 Generators:\n",
      "\t\tFitting FillNaFeatureGenerator...\n",
      "\tStage 3 Generators:\n",
      "\t\tFitting IdentityFeatureGenerator...\n",
      "\tStage 3 Generators:\n",
      "\t\tFitting IdentityFeatureGenerator...\n",
      "\tStage 4 Generators:\n",
      "\t\tFitting DropUniqueFeatureGenerator...\n",
      "\tStage 4 Generators:\n",
      "\t\tFitting DropUniqueFeatureGenerator...\n",
      "\tStage 5 Generators:\n",
      "\t\tFitting DropDuplicatesFeatureGenerator...\n",
      "\tStage 5 Generators:\n",
      "\t\tFitting DropDuplicatesFeatureGenerator...\n",
      "\tUnused Original Features (Count: 1): ['mb_trnflw_top8_transcode_amount_sum']\n",
      "\t\tThese features were not used to generate any of the output features. Add a feature generator compatible with these features to utilize them.\n",
      "\t\tFeatures can also be unused if they carry very little information, such as being categorical but having almost entirely unique values or being duplicates of other features.\n",
      "\t\tThese features do not need to be present at inference time.\n",
      "\t\t('float', []) : 1 | ['mb_trnflw_top8_transcode_amount_sum']\n",
      "\tTypes of features in original data (raw dtype, special dtypes):\n",
      "\t\t('float', []) : 688 | ['ASSET_AUM_CV', 'ASSET_AUM_GROWTH_D2M', 'ASSET_AUM_GROWTH_M2S', 'ASSET_AUM_GROWTH_S2Y', 'ASSET_AUM_TREND_DOWN', ...]\n",
      "\t\t('int', [])   :   7 | ['NATURE_IS_HIGH_RANK', 'NATURE_IS_LOW_RANK', 'NATURE_IS_MIDDLE', 'NATURE_IS_OLD', 'NATURE_IS_YOUNG', ...]\n",
      "\tTypes of features in processed data (raw dtype, special dtypes):\n",
      "\t\t('float', [])     : 659 | ['ASSET_AUM_CV', 'ASSET_AUM_GROWTH_D2M', 'ASSET_AUM_GROWTH_M2S', 'ASSET_AUM_GROWTH_S2Y', 'ASSET_DAY_AUM_CALC', ...]\n",
      "\t\t('int', [])       :   1 | ['NATURE_SEX_RANK_INTERACT']\n",
      "\t\t('int', ['bool']) :  35 | ['ASSET_AUM_TREND_DOWN', 'ASSET_AUM_TREND_UP', 'ASSET_HAS_LOAN', 'ASSET_IS_HIGH_AUM', 'ASSET_IS_HIGH_DEPOSIT', ...]\n",
      "\tUnused Original Features (Count: 1): ['mb_trnflw_top8_transcode_amount_sum']\n",
      "\t\tThese features were not used to generate any of the output features. Add a feature generator compatible with these features to utilize them.\n",
      "\t\tFeatures can also be unused if they carry very little information, such as being categorical but having almost entirely unique values or being duplicates of other features.\n",
      "\t\tThese features do not need to be present at inference time.\n",
      "\t\t('float', []) : 1 | ['mb_trnflw_top8_transcode_amount_sum']\n",
      "\tTypes of features in original data (raw dtype, special dtypes):\n",
      "\t\t('float', []) : 688 | ['ASSET_AUM_CV', 'ASSET_AUM_GROWTH_D2M', 'ASSET_AUM_GROWTH_M2S', 'ASSET_AUM_GROWTH_S2Y', 'ASSET_AUM_TREND_DOWN', ...]\n",
      "\t\t('int', [])   :   7 | ['NATURE_IS_HIGH_RANK', 'NATURE_IS_LOW_RANK', 'NATURE_IS_MIDDLE', 'NATURE_IS_OLD', 'NATURE_IS_YOUNG', ...]\n",
      "\tTypes of features in processed data (raw dtype, special dtypes):\n",
      "\t\t('float', [])     : 659 | ['ASSET_AUM_CV', 'ASSET_AUM_GROWTH_D2M', 'ASSET_AUM_GROWTH_M2S', 'ASSET_AUM_GROWTH_S2Y', 'ASSET_DAY_AUM_CALC', ...]\n",
      "\t\t('int', [])       :   1 | ['NATURE_SEX_RANK_INTERACT']\n",
      "\t\t('int', ['bool']) :  35 | ['ASSET_AUM_TREND_DOWN', 'ASSET_AUM_TREND_UP', 'ASSET_HAS_LOAN', 'ASSET_IS_HIGH_AUM', 'ASSET_IS_HIGH_DEPOSIT', ...]\n",
      "\t2.6s = Fit runtime\n",
      "\t695 features in original data used to generate 695 features in processed data.\n",
      "\t2.6s = Fit runtime\n",
      "\t695 features in original data used to generate 695 features in processed data.\n",
      "\tTrain Data (Processed) Memory Usage: 260.52 MB (0.3% of available memory)\n",
      "\tTrain Data (Processed) Memory Usage: 260.52 MB (0.3% of available memory)\n",
      "Data preprocessing and feature engineering runtime = 2.86s ...\n",
      "AutoGluon will gauge predictive performance using evaluation metric: 'macro_f1'\n",
      "\tTo change this, specify the eval_metric parameter of Predictor()\n",
      "User-specified model hyperparameters to be fit:\n",
      "{\n",
      "\t'GBM': [{'num_boost_round': 200, 'learning_rate': 0.03, 'num_leaves': 31, 'feature_fraction': 0.7, 'bagging_fraction': 0.7, 'bagging_freq': 5, 'min_child_samples': 30, 'lambda_l1': 0.5, 'lambda_l2': 0.5, 'max_depth': 6}, {'num_boost_round': 300, 'learning_rate': 0.02, 'num_leaves': 63, 'feature_fraction': 0.75, 'bagging_fraction': 0.75, 'bagging_freq': 4, 'min_child_samples': 25, 'lambda_l1': 0.4, 'lambda_l2': 0.4, 'max_depth': 7}],\n",
      "\t'CAT': [{'iterations': 200, 'learning_rate': 0.03, 'depth': 5, 'l2_leaf_reg': 5, 'bagging_temperature': 0.5, 'random_strength': 1.0, 'border_count': 64}, {'iterations': 300, 'learning_rate': 0.02, 'depth': 6, 'l2_leaf_reg': 4, 'bagging_temperature': 0.6, 'random_strength': 1.1, 'border_count': 96}],\n",
      "\t'XGB': [{'n_estimators': 200, 'learning_rate': 0.03, 'max_depth': 5, 'colsample_bytree': 0.7, 'subsample': 0.7, 'min_child_weight': 5, 'reg_alpha': 0.5, 'reg_lambda': 0.5, 'gamma': 0.1}, {'n_estimators': 300, 'learning_rate': 0.02, 'max_depth': 6, 'colsample_bytree': 0.75, 'subsample': 0.75, 'min_child_weight': 4, 'reg_alpha': 0.4, 'reg_lambda': 0.4, 'gamma': 0.05}],\n",
      "\t'RF': [{'n_estimators': 200, 'max_depth': 10, 'min_samples_split': 10, 'min_samples_leaf': 5, 'max_features': 'sqrt'}],\n",
      "\t'XT': [{'n_estimators': 200, 'max_depth': 10, 'min_samples_split': 10, 'min_samples_leaf': 5}],\n",
      "}\n",
      "Data preprocessing and feature engineering runtime = 2.86s ...\n",
      "AutoGluon will gauge predictive performance using evaluation metric: 'macro_f1'\n",
      "\tTo change this, specify the eval_metric parameter of Predictor()\n",
      "User-specified model hyperparameters to be fit:\n",
      "{\n",
      "\t'GBM': [{'num_boost_round': 200, 'learning_rate': 0.03, 'num_leaves': 31, 'feature_fraction': 0.7, 'bagging_fraction': 0.7, 'bagging_freq': 5, 'min_child_samples': 30, 'lambda_l1': 0.5, 'lambda_l2': 0.5, 'max_depth': 6}, {'num_boost_round': 300, 'learning_rate': 0.02, 'num_leaves': 63, 'feature_fraction': 0.75, 'bagging_fraction': 0.75, 'bagging_freq': 4, 'min_child_samples': 25, 'lambda_l1': 0.4, 'lambda_l2': 0.4, 'max_depth': 7}],\n",
      "\t'CAT': [{'iterations': 200, 'learning_rate': 0.03, 'depth': 5, 'l2_leaf_reg': 5, 'bagging_temperature': 0.5, 'random_strength': 1.0, 'border_count': 64}, {'iterations': 300, 'learning_rate': 0.02, 'depth': 6, 'l2_leaf_reg': 4, 'bagging_temperature': 0.6, 'random_strength': 1.1, 'border_count': 96}],\n",
      "\t'XGB': [{'n_estimators': 200, 'learning_rate': 0.03, 'max_depth': 5, 'colsample_bytree': 0.7, 'subsample': 0.7, 'min_child_weight': 5, 'reg_alpha': 0.5, 'reg_lambda': 0.5, 'gamma': 0.1}, {'n_estimators': 300, 'learning_rate': 0.02, 'max_depth': 6, 'colsample_bytree': 0.75, 'subsample': 0.75, 'min_child_weight': 4, 'reg_alpha': 0.4, 'reg_lambda': 0.4, 'gamma': 0.05}],\n",
      "\t'RF': [{'n_estimators': 200, 'max_depth': 10, 'min_samples_split': 10, 'min_samples_leaf': 5, 'max_features': 'sqrt'}],\n",
      "\t'XT': [{'n_estimators': 200, 'max_depth': 10, 'min_samples_split': 10, 'min_samples_leaf': 5}],\n",
      "}\n",
      "AutoGluon will fit 2 stack levels (L1 to L2) ...\n",
      "Excluded models: [] (Specified by `excluded_model_types`)\n",
      "Fitting 8 L1 models, fit_strategy=\"sequential\" ...\n",
      "Fitting model: LightGBM_BAG_L1 ... Training model for up to 2397.50s of the 3597.14s of remaining time.\n",
      "AutoGluon will fit 2 stack levels (L1 to L2) ...\n",
      "Excluded models: [] (Specified by `excluded_model_types`)\n",
      "Fitting 8 L1 models, fit_strategy=\"sequential\" ...\n",
      "Fitting model: LightGBM_BAG_L1 ... Training model for up to 2397.50s of the 3597.14s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5654\t = Validation score   (macro_f1)\n",
      "\t67.91s\t = Training   runtime\n",
      "\t0.36s\t = Validation runtime\n",
      "Fitting model: LightGBM_2_BAG_L1 ... Training model for up to 2327.37s of the 3527.01s of remaining time.\n",
      "\t0.5654\t = Validation score   (macro_f1)\n",
      "\t67.91s\t = Training   runtime\n",
      "\t0.36s\t = Validation runtime\n",
      "Fitting model: LightGBM_2_BAG_L1 ... Training model for up to 2327.37s of the 3527.01s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5679\t = Validation score   (macro_f1)\n",
      "\t128.99s\t = Training   runtime\n",
      "\t0.56s\t = Validation runtime\n",
      "Fitting model: RandomForest_BAG_L1 ... Training model for up to 2195.46s of the 3395.11s of remaining time.\n",
      "\t0.5679\t = Validation score   (macro_f1)\n",
      "\t128.99s\t = Training   runtime\n",
      "\t0.56s\t = Validation runtime\n",
      "Fitting model: RandomForest_BAG_L1 ... Training model for up to 2195.46s of the 3395.11s of remaining time.\n",
      "\t0.4552\t = Validation score   (macro_f1)\n",
      "\t3.27s\t = Training   runtime\n",
      "\t7.48s\t = Validation runtime\n",
      "Fitting model: CatBoost_BAG_L1 ... Training model for up to 2184.14s of the 3383.78s of remaining time.\n",
      "\t0.4552\t = Validation score   (macro_f1)\n",
      "\t3.27s\t = Training   runtime\n",
      "\t7.48s\t = Validation runtime\n",
      "Fitting model: CatBoost_BAG_L1 ... Training model for up to 2184.14s of the 3383.78s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.476\t = Validation score   (macro_f1)\n",
      "\t54.01s\t = Training   runtime\n",
      "\t0.08s\t = Validation runtime\n",
      "Fitting model: CatBoost_2_BAG_L1 ... Training model for up to 2128.87s of the 3328.52s of remaining time.\n",
      "\t0.476\t = Validation score   (macro_f1)\n",
      "\t54.01s\t = Training   runtime\n",
      "\t0.08s\t = Validation runtime\n",
      "Fitting model: CatBoost_2_BAG_L1 ... Training model for up to 2128.87s of the 3328.52s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.4761\t = Validation score   (macro_f1)\n",
      "\t234.23s\t = Training   runtime\n",
      "\t0.09s\t = Validation runtime\n",
      "Fitting model: ExtraTrees_BAG_L1 ... Training model for up to 1893.38s of the 3093.03s of remaining time.\n",
      "\t0.4761\t = Validation score   (macro_f1)\n",
      "\t234.23s\t = Training   runtime\n",
      "\t0.09s\t = Validation runtime\n",
      "Fitting model: ExtraTrees_BAG_L1 ... Training model for up to 1893.38s of the 3093.03s of remaining time.\n",
      "\t0.3018\t = Validation score   (macro_f1)\n",
      "\t1.83s\t = Training   runtime\n",
      "\t7.47s\t = Validation runtime\n",
      "Fitting model: XGBoost_BAG_L1 ... Training model for up to 1883.53s of the 3083.18s of remaining time.\n",
      "\t0.3018\t = Validation score   (macro_f1)\n",
      "\t1.83s\t = Training   runtime\n",
      "\t7.47s\t = Validation runtime\n",
      "Fitting model: XGBoost_BAG_L1 ... Training model for up to 1883.53s of the 3083.18s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5585\t = Validation score   (macro_f1)\n",
      "\t125.66s\t = Training   runtime\n",
      "\t1.14s\t = Validation runtime\n",
      "Fitting model: XGBoost_2_BAG_L1 ... Training model for up to 1755.31s of the 2954.96s of remaining time.\n",
      "\t0.5585\t = Validation score   (macro_f1)\n",
      "\t125.66s\t = Training   runtime\n",
      "\t1.14s\t = Validation runtime\n",
      "Fitting model: XGBoost_2_BAG_L1 ... Training model for up to 1755.31s of the 2954.96s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5624\t = Validation score   (macro_f1)\n",
      "\t221.16s\t = Training   runtime\n",
      "\t1.27s\t = Validation runtime\n",
      "\t0.5624\t = Validation score   (macro_f1)\n",
      "\t221.16s\t = Training   runtime\n",
      "\t1.27s\t = Validation runtime\n",
      "Fitting model: WeightedEnsemble_L2 ... Training model for up to 360.00s of the 2730.85s of remaining time.\n",
      "Fitting model: WeightedEnsemble_L2 ... Training model for up to 360.00s of the 2730.85s of remaining time.\n",
      "\tEnsemble Weights: {'LightGBM_2_BAG_L1': 1.0}\n",
      "\t0.5679\t = Validation score   (macro_f1)\n",
      "\t1.73s\t = Training   runtime\n",
      "\t0.01s\t = Validation runtime\n",
      "Excluded models: [] (Specified by `excluded_model_types`)\n",
      "Fitting 8 L2 models, fit_strategy=\"sequential\" ...\n",
      "\tEnsemble Weights: {'LightGBM_2_BAG_L1': 1.0}\n",
      "\t0.5679\t = Validation score   (macro_f1)\n",
      "\t1.73s\t = Training   runtime\n",
      "\t0.01s\t = Validation runtime\n",
      "Excluded models: [] (Specified by `excluded_model_types`)\n",
      "Fitting 8 L2 models, fit_strategy=\"sequential\" ...\n",
      "Fitting model: LightGBM_BAG_L2 ... Training model for up to 2729.08s of the 2729.00s of remaining time.\n",
      "Fitting model: LightGBM_BAG_L2 ... Training model for up to 2729.08s of the 2729.00s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5809\t = Validation score   (macro_f1)\n",
      "\t60.52s\t = Training   runtime\n",
      "\t0.23s\t = Validation runtime\n",
      "Fitting model: LightGBM_2_BAG_L2 ... Training model for up to 2666.57s of the 2666.49s of remaining time.\n",
      "\t0.5809\t = Validation score   (macro_f1)\n",
      "\t60.52s\t = Training   runtime\n",
      "\t0.23s\t = Validation runtime\n",
      "Fitting model: LightGBM_2_BAG_L2 ... Training model for up to 2666.57s of the 2666.49s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.581\t = Validation score   (macro_f1)\n",
      "\t103.55s\t = Training   runtime\n",
      "\t0.35s\t = Validation runtime\n",
      "\t0.581\t = Validation score   (macro_f1)\n",
      "\t103.55s\t = Training   runtime\n",
      "\t0.35s\t = Validation runtime\n",
      "Fitting model: RandomForest_BAG_L2 ... Training model for up to 2560.61s of the 2560.53s of remaining time.\n",
      "Fitting model: RandomForest_BAG_L2 ... Training model for up to 2560.61s of the 2560.53s of remaining time.\n",
      "\t0.5485\t = Validation score   (macro_f1)\n",
      "\t3.97s\t = Training   runtime\n",
      "\t8.53s\t = Validation runtime\n",
      "Fitting model: CatBoost_BAG_L2 ... Training model for up to 2547.46s of the 2547.38s of remaining time.\n",
      "\t0.5485\t = Validation score   (macro_f1)\n",
      "\t3.97s\t = Training   runtime\n",
      "\t8.53s\t = Validation runtime\n",
      "Fitting model: CatBoost_BAG_L2 ... Training model for up to 2547.46s of the 2547.38s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5563\t = Validation score   (macro_f1)\n",
      "\t62.67s\t = Training   runtime\n",
      "\t0.09s\t = Validation runtime\n",
      "Fitting model: CatBoost_2_BAG_L2 ... Training model for up to 2483.36s of the 2483.28s of remaining time.\n",
      "\t0.5563\t = Validation score   (macro_f1)\n",
      "\t62.67s\t = Training   runtime\n",
      "\t0.09s\t = Validation runtime\n",
      "Fitting model: CatBoost_2_BAG_L2 ... Training model for up to 2483.36s of the 2483.28s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5536\t = Validation score   (macro_f1)\n",
      "\t298.63s\t = Training   runtime\n",
      "\t0.09s\t = Validation runtime\n",
      "\t0.5536\t = Validation score   (macro_f1)\n",
      "\t298.63s\t = Training   runtime\n",
      "\t0.09s\t = Validation runtime\n",
      "Fitting model: ExtraTrees_BAG_L2 ... Training model for up to 2183.30s of the 2183.22s of remaining time.\n",
      "Fitting model: ExtraTrees_BAG_L2 ... Training model for up to 2183.30s of the 2183.22s of remaining time.\n",
      "\t0.5104\t = Validation score   (macro_f1)\n",
      "\t1.92s\t = Training   runtime\n",
      "\t8.24s\t = Validation runtime\n",
      "Fitting model: XGBoost_BAG_L2 ... Training model for up to 2172.51s of the 2172.43s of remaining time.\n",
      "\t0.5104\t = Validation score   (macro_f1)\n",
      "\t1.92s\t = Training   runtime\n",
      "\t8.24s\t = Validation runtime\n",
      "Fitting model: XGBoost_BAG_L2 ... Training model for up to 2172.51s of the 2172.43s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5865\t = Validation score   (macro_f1)\n",
      "\t131.35s\t = Training   runtime\n",
      "\t1.19s\t = Validation runtime\n",
      "\t0.5865\t = Validation score   (macro_f1)\n",
      "\t131.35s\t = Training   runtime\n",
      "\t1.19s\t = Validation runtime\n",
      "Fitting model: XGBoost_2_BAG_L2 ... Training model for up to 2038.44s of the 2038.36s of remaining time.\n",
      "Fitting model: XGBoost_2_BAG_L2 ... Training model for up to 2038.44s of the 2038.36s of remaining time.\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\tFitting 5 child models (S1F1 - S1F5) | Fitting with SequentialLocalFoldFittingStrategy\n",
      "\t0.5856\t = Validation score   (macro_f1)\n",
      "\t190.97s\t = Training   runtime\n",
      "\t1.22s\t = Validation runtime\n",
      "\t0.5856\t = Validation score   (macro_f1)\n",
      "\t190.97s\t = Training   runtime\n",
      "\t1.22s\t = Validation runtime\n",
      "Fitting model: WeightedEnsemble_L3 ... Training model for up to 360.00s of the 1844.49s of remaining time.\n",
      "Fitting model: WeightedEnsemble_L3 ... Training model for up to 360.00s of the 1844.49s of remaining time.\n",
      "\tEnsemble Weights: {'XGBoost_BAG_L2': 1.0}\n",
      "\t0.5865\t = Validation score   (macro_f1)\n",
      "\t3.47s\t = Training   runtime\n",
      "\t0.01s\t = Validation runtime\n",
      "AutoGluon training complete, total runtime = 1759.26s ... Best model: WeightedEnsemble_L3 | Estimated inference throughput: 1340.0 rows/s (10280 batch size)\n",
      "\tEnsemble Weights: {'XGBoost_BAG_L2': 1.0}\n",
      "\t0.5865\t = Validation score   (macro_f1)\n",
      "\t3.47s\t = Training   runtime\n",
      "\t0.01s\t = Validation runtime\n",
      "AutoGluon training complete, total runtime = 1759.26s ... Best model: WeightedEnsemble_L3 | Estimated inference throughput: 1340.0 rows/s (10280 batch size)\n",
      "TabularPredictor saved. To load, use: predictor = TabularPredictor.load(\"e:\\DevWork\\6-Model\\star-cup2025\\Model\\model\\autogluon_optimized\")\n",
      "TabularPredictor saved. To load, use: predictor = TabularPredictor.load(\"e:\\DevWork\\6-Model\\star-cup2025\\Model\\model\\autogluon_optimized\")\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "训练耗时: 29.32 分钟\n",
      "====================================================================================================\n",
      "模型训练完成!\n",
      "====================================================================================================\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤7: 优化后的Autogluon模型训练（防过拟合版本）\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 创建输出目录\n",
    "output_dir_optimized = './model/autogluon_optimized'\n",
    "os.makedirs(output_dir_optimized, exist_ok=True)\n",
    "\n",
    "print(f\"\\n模型输出目录: {output_dir_optimized}\")\n",
    "print(\"\\n优化策略:\")\n",
    "print(\"  1. 降低模型复杂度：medium_quality preset\")\n",
    "print(\"  2. 减少集成层数：1层Stacking + 1组Bagging\")\n",
    "print(\"  3. 增强正则化：更高的L1/L2系数，更深的树限制\")\n",
    "print(\"  4. 排除易过拟合模型：KNN、神经网络\")\n",
    "print(\"  5. 更保守的超参数设置\")\n",
    "print(\"\\n预计训练时间: 45-60分钟\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 定义优化后的超参数（更保守，防过拟合）\n",
    "hyperparameters_optimized = {\n",
    "    # LightGBM - 增强正则化\n",
    "    'GBM': [\n",
    "        {\n",
    "            'num_boost_round': 200,      # 减少迭代次数\n",
    "            'learning_rate': 0.03,        # 降低学习率\n",
    "            'num_leaves': 31,             # 限制叶子数\n",
    "            'feature_fraction': 0.7,      # 降低特征采样\n",
    "            'bagging_fraction': 0.7,      # 降低样本采样\n",
    "            'bagging_freq': 5,\n",
    "            'min_child_samples': 30,      # 增加最小样本数\n",
    "            'lambda_l1': 0.5,             # 增强L1正则化\n",
    "            'lambda_l2': 0.5,             # 增强L2正则化\n",
    "            'max_depth': 6,               # 限制树深度\n",
    "        },\n",
    "        {\n",
    "            'num_boost_round': 300,\n",
    "            'learning_rate': 0.02,\n",
    "            'num_leaves': 63,\n",
    "            'feature_fraction': 0.75,\n",
    "            'bagging_fraction': 0.75,\n",
    "            'bagging_freq': 4,\n",
    "            'min_child_samples': 25,\n",
    "            'lambda_l1': 0.4,\n",
    "            'lambda_l2': 0.4,\n",
    "            'max_depth': 7,\n",
    "        },\n",
    "    ],\n",
    "    \n",
    "    # CatBoost - 增强正则化\n",
    "    'CAT': [\n",
    "        {\n",
    "            'iterations': 200,\n",
    "            'learning_rate': 0.03,\n",
    "            'depth': 5,                   # 限制树深度\n",
    "            'l2_leaf_reg': 5,             # 增强L2正则化\n",
    "            'bagging_temperature': 0.5,   # 降低随机性\n",
    "            'random_strength': 1.0,\n",
    "            'border_count': 64,\n",
    "        },\n",
    "        {\n",
    "            'iterations': 300,\n",
    "            'learning_rate': 0.02,\n",
    "            'depth': 6,\n",
    "            'l2_leaf_reg': 4,\n",
    "            'bagging_temperature': 0.6,\n",
    "            'random_strength': 1.1,\n",
    "            'border_count': 96,\n",
    "        },\n",
    "    ],\n",
    "    \n",
    "    # XGBoost - 增强正则化\n",
    "    'XGB': [\n",
    "        {\n",
    "            'n_estimators': 200,\n",
    "            'learning_rate': 0.03,\n",
    "            'max_depth': 5,               # 限制树深度\n",
    "            'colsample_bytree': 0.7,      # 降低特征采样\n",
    "            'subsample': 0.7,             # 降低样本采样\n",
    "            'min_child_weight': 5,        # 增加最小权重\n",
    "            'reg_alpha': 0.5,             # 增强L1正则化\n",
    "            'reg_lambda': 0.5,            # 增强L2正则化\n",
    "            'gamma': 0.1,                 # 增加分裂惩罚\n",
    "        },\n",
    "        {\n",
    "            'n_estimators': 300,\n",
    "            'learning_rate': 0.02,\n",
    "            'max_depth': 6,\n",
    "            'colsample_bytree': 0.75,\n",
    "            'subsample': 0.75,\n",
    "            'min_child_weight': 4,\n",
    "            'reg_alpha': 0.4,\n",
    "            'reg_lambda': 0.4,\n",
    "            'gamma': 0.05,\n",
    "        },\n",
    "    ],\n",
    "    \n",
    "    # 随机森林 - 保守设置\n",
    "    'RF': [\n",
    "        {\n",
    "            'n_estimators': 200,\n",
    "            'max_depth': 10,              # 限制深度\n",
    "            'min_samples_split': 10,      # 增加分裂最小样本\n",
    "            'min_samples_leaf': 5,        # 增加叶子最小样本\n",
    "            'max_features': 'sqrt',       # 使用平方根特征数\n",
    "        },\n",
    "    ],\n",
    "    \n",
    "    # ExtraTrees - 保守设置\n",
    "    'XT': [\n",
    "        {\n",
    "            'n_estimators': 200,\n",
    "            'max_depth': 10,\n",
    "            'min_samples_split': 10,\n",
    "            'min_samples_leaf': 5,\n",
    "        },\n",
    "    ],\n",
    "}\n",
    "\n",
    "print(\"\\n开始训练...\")\n",
    "start_time = time.time()\n",
    "\n",
    "predictor_optimized = TabularPredictor(\n",
    "    label='FLAG',\n",
    "    problem_type='multiclass',\n",
    "    eval_metric=macro_f1_scorer,\n",
    "    path=output_dir_optimized,\n",
    "    verbosity=2\n",
    ").fit(\n",
    "    train_data=train_data_ag,\n",
    "    time_limit=3600,              # 1小时\n",
    "    presets='medium_quality',     # 从best_quality改为medium_quality\n",
    "    num_bag_folds=5,              # 5折交叉验证\n",
    "    num_bag_sets=1,               # 从2组改为1组\n",
    "    num_stack_levels=1,           # 从2层改为1层\n",
    "    hyperparameters=hyperparameters_optimized,\n",
    "    ag_args_fit={\n",
    "        'num_gpus': 0,\n",
    "    },\n",
    "    excluded_model_types=['KNN', 'NN_TORCH', 'FASTAI'],  # 排除易过拟合的模型\n",
    ")\n",
    "\n",
    "training_time = time.time() - start_time\n",
    "print(f\"\\n训练耗时: {training_time/60:.2f} 分钟\")\n",
    "print(\"=\"*100)\n",
    "print(\"模型训练完成!\")\n",
    "print(\"=\"*100)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f6b81581",
   "metadata": {},
   "source": [
    "## 步骤8：模型评估"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "d77404a0",
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "These features in provided data are not utilized by the predictor and will be ignored: ['mb_trnflw_top8_transcode_amount_sum']\n",
      "Computing feature importance via permutation shuffling for 695 features using 5000 rows with 1 shuffle sets... Time limit: 120s...\n",
      "Computing feature importance via permutation shuffling for 695 features using 5000 rows with 1 shuffle sets... Time limit: 120s...\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤8: 模型评估\n",
      "====================================================================================================\n",
      "\n",
      "模型排行榜 (Top 15):\n",
      "                  model  score_val eval_metric  pred_time_val     fit_time  \\\n",
      "0        XGBoost_BAG_L2   0.586511    macro_f1      19.623287   968.399177   \n",
      "1   WeightedEnsemble_L3   0.586511    macro_f1      19.634289   971.865785   \n",
      "2      XGBoost_2_BAG_L2   0.585629    macro_f1      19.655432  1028.020785   \n",
      "3     LightGBM_2_BAG_L2   0.580953    macro_f1      18.783114   940.605446   \n",
      "4       LightGBM_BAG_L2   0.580861    macro_f1      18.662453   897.576474   \n",
      "5     LightGBM_2_BAG_L1   0.567917    macro_f1       0.561888   128.985771   \n",
      "6   WeightedEnsemble_L2   0.567917    macro_f1       0.569888   130.717274   \n",
      "7       LightGBM_BAG_L1   0.565409    macro_f1       0.356180    67.909031   \n",
      "8      XGBoost_2_BAG_L1   0.562425    macro_f1       1.270688   221.158354   \n",
      "9        XGBoost_BAG_L1   0.558498    macro_f1       1.137312   125.657415   \n",
      "10      CatBoost_BAG_L2   0.556302    macro_f1      18.520226   899.723581   \n",
      "11    CatBoost_2_BAG_L2   0.553600    macro_f1      18.527963  1135.677871   \n",
      "12  RandomForest_BAG_L2   0.548518    macro_f1      26.961198   841.027666   \n",
      "13    ExtraTrees_BAG_L2   0.510420    macro_f1      26.671128   838.969064   \n",
      "14    CatBoost_2_BAG_L1   0.476069    macro_f1       0.088281   234.234709   \n",
      "\n",
      "    pred_time_val_marginal  fit_time_marginal  stack_level  can_infer  \\\n",
      "0                 1.189645         131.346400            2       True   \n",
      "1                 0.011002           3.466608            3       True   \n",
      "2                 1.221790         190.968008            2       True   \n",
      "3                 0.349473         103.552669            2       True   \n",
      "4                 0.228811          60.523697            2       True   \n",
      "5                 0.561888         128.985771            1       True   \n",
      "6                 0.008000           1.731503            2       True   \n",
      "7                 0.356180          67.909031            1       True   \n",
      "8                 1.270688         221.158354            1       True   \n",
      "9                 1.137312         125.657415            1       True   \n",
      "10                0.086584          62.670805            2       True   \n",
      "11                0.094321         298.625095            2       True   \n",
      "12                8.527556           3.974890            2       True   \n",
      "13                8.237486           1.916287            2       True   \n",
      "14                0.088281         234.234709            1       True   \n",
      "\n",
      "    fit_order  \n",
      "0          16  \n",
      "1          18  \n",
      "2          17  \n",
      "3          11  \n",
      "4          10  \n",
      "5           2  \n",
      "6           9  \n",
      "7           1  \n",
      "8           8  \n",
      "9           7  \n",
      "10         13  \n",
      "11         14  \n",
      "12         12  \n",
      "13         15  \n",
      "14          5  \n",
      "\n",
      "最佳模型: XGBoost_BAG_L2\n",
      "验证集 Macro-F1 分数: 0.586511\n",
      "\n",
      "计算特征重要性（优化参数，加速计算）...\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\t3060.08s\t= Expected runtime (3060.08s per shuffle set)\n"
     ]
    },
    {
     "ename": "KeyboardInterrupt",
     "evalue": "",
     "output_type": "error",
     "traceback": [
      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m                         Traceback (most recent call last)",
      "Cell \u001b[1;32mIn[17], line 20\u001b[0m\n\u001b[0;32m     18\u001b[0m \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124m计算特征重要性（优化参数，加速计算）...\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m     19\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m---> 20\u001b[0m     feature_importance \u001b[38;5;241m=\u001b[39m \u001b[43mpredictor_optimized\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mfeature_importance\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m     21\u001b[0m \u001b[43m        \u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtrain_data_ag\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m     22\u001b[0m \u001b[43m        \u001b[49m\u001b[43msubsample_size\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m5000\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m     \u001b[49m\u001b[38;5;66;43;03m# 只使用5000个样本\u001b[39;49;00m\n\u001b[0;32m     23\u001b[0m \u001b[43m        \u001b[49m\u001b[43mnum_shuffle_sets\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m      \u001b[49m\u001b[38;5;66;43;03m# 只shuffle 1次\u001b[39;49;00m\n\u001b[0;32m     24\u001b[0m \u001b[43m        \u001b[49m\u001b[43mtime_limit\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;241;43m120\u001b[39;49m\u001b[43m,\u001b[49m\u001b[43m          \u001b[49m\u001b[38;5;66;43;03m# 限制2分钟\u001b[39;49;00m\n\u001b[0;32m     25\u001b[0m \u001b[43m        \u001b[49m\u001b[43msilent\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\n\u001b[0;32m     26\u001b[0m \u001b[43m    \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m     28\u001b[0m     \u001b[38;5;28mprint\u001b[39m(\u001b[38;5;124mf\u001b[39m\u001b[38;5;124m\"\u001b[39m\u001b[38;5;130;01m\\n\u001b[39;00m\u001b[38;5;124mTop 30 重要特征:\u001b[39m\u001b[38;5;124m\"\u001b[39m)\n\u001b[0;32m     29\u001b[0m     \u001b[38;5;28mprint\u001b[39m(feature_importance\u001b[38;5;241m.\u001b[39mhead(\u001b[38;5;241m30\u001b[39m))\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\predictor\\predictor.py:3428\u001b[0m, in \u001b[0;36mTabularPredictor.feature_importance\u001b[1;34m(self, data, model, features, feature_stage, subsample_size, time_limit, num_shuffle_sets, include_confidence_band, confidence_level, silent)\u001b[0m\n\u001b[0;32m   3425\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m num_shuffle_sets \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m   3426\u001b[0m     num_shuffle_sets \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m10\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m time_limit \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;241m5\u001b[39m\n\u001b[1;32m-> 3428\u001b[0m fi_df \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_learner\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_feature_importance\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   3429\u001b[0m \u001b[43m    \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3430\u001b[0m \u001b[43m    \u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3431\u001b[0m \u001b[43m    \u001b[49m\u001b[43mfeatures\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mfeatures\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3432\u001b[0m \u001b[43m    \u001b[49m\u001b[43mfeature_stage\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mfeature_stage\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3433\u001b[0m \u001b[43m    \u001b[49m\u001b[43msubsample_size\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msubsample_size\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3434\u001b[0m \u001b[43m    \u001b[49m\u001b[43mtime_limit\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mtime_limit\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3435\u001b[0m \u001b[43m    \u001b[49m\u001b[43mnum_shuffle_sets\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnum_shuffle_sets\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3436\u001b[0m \u001b[43m    \u001b[49m\u001b[43msilent\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43msilent\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   3437\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   3439\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m include_confidence_band:\n\u001b[0;32m   3440\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m confidence_level \u001b[38;5;241m<\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0.5\u001b[39m \u001b[38;5;129;01mor\u001b[39;00m confidence_level \u001b[38;5;241m>\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[38;5;241m1.0\u001b[39m:\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\learner\\abstract_learner.py:1010\u001b[0m, in \u001b[0;36mAbstractTabularLearner.get_feature_importance\u001b[1;34m(self, model, X, y, features, feature_stage, subsample_size, silent, **kwargs)\u001b[0m\n\u001b[0;32m   1007\u001b[0m         X \u001b[38;5;241m=\u001b[39m X\u001b[38;5;241m.\u001b[39mdrop(columns\u001b[38;5;241m=\u001b[39munused_features)\n\u001b[0;32m   1009\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m feature_stage \u001b[38;5;241m==\u001b[39m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124moriginal\u001b[39m\u001b[38;5;124m\"\u001b[39m:\n\u001b[1;32m-> 1010\u001b[0m         \u001b[38;5;28;01mreturn\u001b[39;00m trainer\u001b[38;5;241m.\u001b[39m_get_feature_importance_raw(\n\u001b[0;32m   1011\u001b[0m             model\u001b[38;5;241m=\u001b[39mmodel, X\u001b[38;5;241m=\u001b[39mX, y\u001b[38;5;241m=\u001b[39my, features\u001b[38;5;241m=\u001b[39mfeatures, subsample_size\u001b[38;5;241m=\u001b[39msubsample_size, transform_func\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtransform_features, silent\u001b[38;5;241m=\u001b[39msilent, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs\n\u001b[0;32m   1012\u001b[0m         )\n\u001b[0;32m   1013\u001b[0m     X \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtransform_features(X)\n\u001b[0;32m   1014\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\trainer\\abstract_trainer.py:3520\u001b[0m, in \u001b[0;36mAbstractTabularTrainer._get_feature_importance_raw\u001b[1;34m(self, X, y, model, eval_metric, **kwargs)\u001b[0m\n\u001b[0;32m   3518\u001b[0m model: AbstractModel \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mload_model(model)\n\u001b[0;32m   3519\u001b[0m predict_func_kwargs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(model\u001b[38;5;241m=\u001b[39mmodel)\n\u001b[1;32m-> 3520\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m compute_permutation_feature_importance(\n\u001b[0;32m   3521\u001b[0m     X\u001b[38;5;241m=\u001b[39mX,\n\u001b[0;32m   3522\u001b[0m     y\u001b[38;5;241m=\u001b[39my,\n\u001b[0;32m   3523\u001b[0m     predict_func\u001b[38;5;241m=\u001b[39mpredict_func,\n\u001b[0;32m   3524\u001b[0m     predict_func_kwargs\u001b[38;5;241m=\u001b[39mpredict_func_kwargs,\n\u001b[0;32m   3525\u001b[0m     eval_metric\u001b[38;5;241m=\u001b[39meval_metric,\n\u001b[0;32m   3526\u001b[0m     quantile_levels\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mquantile_levels,\n\u001b[0;32m   3527\u001b[0m     \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs,\n\u001b[0;32m   3528\u001b[0m )\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\core\\utils\\utils.py:1025\u001b[0m, in \u001b[0;36mcompute_permutation_feature_importance\u001b[1;34m(X, y, predict_func, eval_metric, features, subsample_size, num_shuffle_sets, predict_func_kwargs, transform_func, transform_func_kwargs, time_limit, silent, log_prefix, importance_as_list, random_state, **kwargs)\u001b[0m\n\u001b[0;32m   1023\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m   1024\u001b[0m     X_raw_transformed \u001b[38;5;241m=\u001b[39m X_raw \u001b[38;5;28;01mif\u001b[39;00m transform_func \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;28;01melse\u001b[39;00m transform_func(X_raw, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mtransform_func_kwargs)\n\u001b[1;32m-> 1025\u001b[0m y_pred \u001b[38;5;241m=\u001b[39m predict_func(X_raw_transformed, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mpredict_func_kwargs)\n\u001b[0;32m   1027\u001b[0m row_index \u001b[38;5;241m=\u001b[39m \u001b[38;5;241m0\u001b[39m\n\u001b[0;32m   1028\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m feature \u001b[38;5;129;01min\u001b[39;00m parallel_computed_features:\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\trainer\\abstract_trainer.py:973\u001b[0m, in \u001b[0;36mAbstractTabularTrainer.predict\u001b[1;34m(self, X, model)\u001b[0m\n\u001b[0;32m    971\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m model \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m    972\u001b[0m     model \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_get_best()\n\u001b[1;32m--> 973\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_predict_model\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m)\u001b[49m\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\trainer\\abstract_trainer.py:3331\u001b[0m, in \u001b[0;36mAbstractTabularTrainer._predict_model\u001b[1;34m(self, X, model, model_pred_proba_dict)\u001b[0m\n\u001b[0;32m   3330\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21m_predict_model\u001b[39m(\u001b[38;5;28mself\u001b[39m, X: pd\u001b[38;5;241m.\u001b[39mDataFrame, model: \u001b[38;5;28mstr\u001b[39m, model_pred_proba_dict: \u001b[38;5;28mdict\u001b[39m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m np\u001b[38;5;241m.\u001b[39mndarray:\n\u001b[1;32m-> 3331\u001b[0m     y_pred_proba \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43m_predict_proba_model\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel_pred_proba_dict\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel_pred_proba_dict\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   3332\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m get_pred_from_proba(y_pred_proba\u001b[38;5;241m=\u001b[39my_pred_proba, problem_type\u001b[38;5;241m=\u001b[39m\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mproblem_type)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\trainer\\abstract_trainer.py:3335\u001b[0m, in \u001b[0;36mAbstractTabularTrainer._predict_proba_model\u001b[1;34m(self, X, model, model_pred_proba_dict)\u001b[0m\n\u001b[0;32m   3334\u001b[0m \u001b[38;5;28;01mdef\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21m_predict_proba_model\u001b[39m(\u001b[38;5;28mself\u001b[39m, X: pd\u001b[38;5;241m.\u001b[39mDataFrame, model: \u001b[38;5;28mstr\u001b[39m, model_pred_proba_dict: \u001b[38;5;28mdict\u001b[39m \u001b[38;5;241m|\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;241m-\u001b[39m\u001b[38;5;241m>\u001b[39m np\u001b[38;5;241m.\u001b[39mndarray:\n\u001b[1;32m-> 3335\u001b[0m     model_pred_proba_dict \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_model_pred_proba_dict\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodels\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m[\u001b[49m\u001b[43mmodel\u001b[49m\u001b[43m]\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mmodel_pred_proba_dict\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mmodel_pred_proba_dict\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   3336\u001b[0m     \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(model, \u001b[38;5;28mstr\u001b[39m):\n\u001b[0;32m   3337\u001b[0m         model \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mname\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\trainer\\abstract_trainer.py:1253\u001b[0m, in \u001b[0;36mAbstractTabularTrainer.get_model_pred_proba_dict\u001b[1;34m(self, X, models, model_pred_proba_dict, model_pred_time_dict, record_pred_time, use_val_cache)\u001b[0m\n\u001b[0;32m   1251\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28misinstance\u001b[39m(model, StackerEnsembleModel):\n\u001b[0;32m   1252\u001b[0m     preprocess_kwargs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mdict\u001b[39m(infer\u001b[38;5;241m=\u001b[39m\u001b[38;5;28;01mFalse\u001b[39;00m, model_pred_proba_dict\u001b[38;5;241m=\u001b[39mmodel_pred_proba_dict)\n\u001b[1;32m-> 1253\u001b[0m     model_pred_proba_dict[model_name] \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mpredict_proba(X, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mpreprocess_kwargs)\n\u001b[0;32m   1254\u001b[0m \u001b[38;5;28;01melse\u001b[39;00m:\n\u001b[0;32m   1255\u001b[0m     model_pred_proba_dict[model_name] \u001b[38;5;241m=\u001b[39m model\u001b[38;5;241m.\u001b[39mpredict_proba(X)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\core\\models\\abstract\\abstract_model.py:1173\u001b[0m, in \u001b[0;36mAbstractModel.predict_proba\u001b[1;34m(self, X, normalize, record_time, **kwargs)\u001b[0m\n\u001b[0;32m   1148\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m   1149\u001b[0m \u001b[38;5;124;03mReturns class prediction probabilities of X.\u001b[39;00m\n\u001b[0;32m   1150\u001b[0m \u001b[38;5;124;03mFor binary problems, this returns the positive class label probability as a 1d numpy array.\u001b[39;00m\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   1169\u001b[0m \u001b[38;5;124;03m    The prediction probabilities\u001b[39;00m\n\u001b[0;32m   1170\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m   1171\u001b[0m time_start \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime() \u001b[38;5;28;01mif\u001b[39;00m record_time \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m-> 1173\u001b[0m y_pred_proba \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_predict_proba_internal(X\u001b[38;5;241m=\u001b[39mX, normalize\u001b[38;5;241m=\u001b[39mnormalize, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m   1175\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparams_aux\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtemperature_scalar\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m   1176\u001b[0m     y_pred_proba \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_apply_temperature_scaling(y_pred_proba)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\core\\models\\ensemble\\bagged_ensemble_model.py:591\u001b[0m, in \u001b[0;36mBaggedEnsembleModel._predict_proba_internal\u001b[1;34m(self, X, normalize, **kwargs)\u001b[0m\n\u001b[0;32m    589\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m model \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodels[\u001b[38;5;241m1\u001b[39m:]:\n\u001b[0;32m    590\u001b[0m     model \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mload_child(model)\n\u001b[1;32m--> 591\u001b[0m     y_pred_proba \u001b[38;5;241m+\u001b[39m\u001b[38;5;241m=\u001b[39m \u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpredict_proba\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mpreprocess_nonadaptive\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43;01mFalse\u001b[39;49;00m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mnormalize\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mnormalize\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    592\u001b[0m y_pred_proba \u001b[38;5;241m=\u001b[39m y_pred_proba \u001b[38;5;241m/\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mn_children\n\u001b[0;32m    593\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m y_pred_proba\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\core\\models\\abstract\\abstract_model.py:1173\u001b[0m, in \u001b[0;36mAbstractModel.predict_proba\u001b[1;34m(self, X, normalize, record_time, **kwargs)\u001b[0m\n\u001b[0;32m   1148\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m   1149\u001b[0m \u001b[38;5;124;03mReturns class prediction probabilities of X.\u001b[39;00m\n\u001b[0;32m   1150\u001b[0m \u001b[38;5;124;03mFor binary problems, this returns the positive class label probability as a 1d numpy array.\u001b[39;00m\n\u001b[1;32m   (...)\u001b[0m\n\u001b[0;32m   1169\u001b[0m \u001b[38;5;124;03m    The prediction probabilities\u001b[39;00m\n\u001b[0;32m   1170\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[0;32m   1171\u001b[0m time_start \u001b[38;5;241m=\u001b[39m time\u001b[38;5;241m.\u001b[39mtime() \u001b[38;5;28;01mif\u001b[39;00m record_time \u001b[38;5;28;01melse\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[1;32m-> 1173\u001b[0m y_pred_proba \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_predict_proba_internal(X\u001b[38;5;241m=\u001b[39mX, normalize\u001b[38;5;241m=\u001b[39mnormalize, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m   1175\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mparams_aux\u001b[38;5;241m.\u001b[39mget(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mtemperature_scalar\u001b[39m\u001b[38;5;124m\"\u001b[39m, \u001b[38;5;28;01mNone\u001b[39;00m) \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m   1176\u001b[0m     y_pred_proba \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_apply_temperature_scaling(y_pred_proba)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\core\\models\\abstract\\abstract_model.py:1187\u001b[0m, in \u001b[0;36mAbstractModel._predict_proba_internal\u001b[1;34m(self, X, normalize, **kwargs)\u001b[0m\n\u001b[0;32m   1185\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m normalize \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n\u001b[0;32m   1186\u001b[0m     normalize \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mnormalize_pred_probas\n\u001b[1;32m-> 1187\u001b[0m y_pred_proba \u001b[38;5;241m=\u001b[39m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_predict_proba(X\u001b[38;5;241m=\u001b[39mX, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m   1188\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m normalize:\n\u001b[0;32m   1189\u001b[0m     y_pred_proba \u001b[38;5;241m=\u001b[39m normalize_pred_probas(y_pred_proba, \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mproblem_type)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\autogluon\\tabular\\models\\xgboost\\xgboost_model.py:225\u001b[0m, in \u001b[0;36mXGBoostModel._predict_proba\u001b[1;34m(self, X, num_cpus, **kwargs)\u001b[0m\n\u001b[0;32m    222\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mproblem_type \u001b[38;5;241m==\u001b[39m REGRESSION:\n\u001b[0;32m    223\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mmodel\u001b[38;5;241m.\u001b[39mpredict(X)\n\u001b[1;32m--> 225\u001b[0m y_pred_proba \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmodel\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpredict_proba\u001b[49m\u001b[43m(\u001b[49m\u001b[43mX\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m    226\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_convert_proba_to_unified_form(y_pred_proba)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\xgboost\\sklearn.py:1798\u001b[0m, in \u001b[0;36mXGBClassifier.predict_proba\u001b[1;34m(self, X, validate_features, base_margin, iteration_range)\u001b[0m\n\u001b[0;32m   1796\u001b[0m     class_prob \u001b[38;5;241m=\u001b[39m softmax(raw_predt, axis\u001b[38;5;241m=\u001b[39m\u001b[38;5;241m1\u001b[39m)\n\u001b[0;32m   1797\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m class_prob\n\u001b[1;32m-> 1798\u001b[0m class_probs \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43msuper\u001b[39;49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpredict\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   1799\u001b[0m \u001b[43m    \u001b[49m\u001b[43mX\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1800\u001b[0m \u001b[43m    \u001b[49m\u001b[43mvalidate_features\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mvalidate_features\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1801\u001b[0m \u001b[43m    \u001b[49m\u001b[43mbase_margin\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbase_margin\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1802\u001b[0m \u001b[43m    \u001b[49m\u001b[43miteration_range\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43miteration_range\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1803\u001b[0m \u001b[43m\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1804\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m _cls_predict_proba(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mn_classes_, class_probs, np\u001b[38;5;241m.\u001b[39mvstack)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\xgboost\\core.py:729\u001b[0m, in \u001b[0;36mrequire_keyword_args.<locals>.throw_if.<locals>.inner_f\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m    727\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, arg \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(sig\u001b[38;5;241m.\u001b[39mparameters, args):\n\u001b[0;32m    728\u001b[0m     kwargs[k] \u001b[38;5;241m=\u001b[39m arg\n\u001b[1;32m--> 729\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m func(\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\xgboost\\sklearn.py:1327\u001b[0m, in \u001b[0;36mXGBModel.predict\u001b[1;34m(self, X, output_margin, validate_features, base_margin, iteration_range)\u001b[0m\n\u001b[0;32m   1325\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39m_can_use_inplace_predict():\n\u001b[0;32m   1326\u001b[0m     \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m-> 1327\u001b[0m         predts \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mget_booster\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43minplace_predict\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   1328\u001b[0m \u001b[43m            \u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mX\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1329\u001b[0m \u001b[43m            \u001b[49m\u001b[43miteration_range\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43miteration_range\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1330\u001b[0m \u001b[43m            \u001b[49m\u001b[43mpredict_type\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mmargin\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01mif\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[43moutput_margin\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;28;43;01melse\u001b[39;49;00m\u001b[43m \u001b[49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[38;5;124;43mvalue\u001b[39;49m\u001b[38;5;124;43m\"\u001b[39;49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1331\u001b[0m \u001b[43m            \u001b[49m\u001b[43mmissing\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mmissing\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1332\u001b[0m \u001b[43m            \u001b[49m\u001b[43mbase_margin\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mbase_margin\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1333\u001b[0m \u001b[43m            \u001b[49m\u001b[43mvalidate_features\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43mvalidate_features\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   1334\u001b[0m \u001b[43m        \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   1335\u001b[0m         \u001b[38;5;28;01mif\u001b[39;00m _is_cupy_alike(predts):\n\u001b[0;32m   1336\u001b[0m             cp \u001b[38;5;241m=\u001b[39m import_cupy()\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\xgboost\\core.py:729\u001b[0m, in \u001b[0;36mrequire_keyword_args.<locals>.throw_if.<locals>.inner_f\u001b[1;34m(*args, **kwargs)\u001b[0m\n\u001b[0;32m    727\u001b[0m \u001b[38;5;28;01mfor\u001b[39;00m k, arg \u001b[38;5;129;01min\u001b[39;00m \u001b[38;5;28mzip\u001b[39m(sig\u001b[38;5;241m.\u001b[39mparameters, args):\n\u001b[0;32m    728\u001b[0m     kwargs[k] \u001b[38;5;241m=\u001b[39m arg\n\u001b[1;32m--> 729\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m func(\u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n",
      "File \u001b[1;32mg:\\Anaconda3\\envs\\starcup\\lib\\site-packages\\xgboost\\core.py:2716\u001b[0m, in \u001b[0;36mBooster.inplace_predict\u001b[1;34m(self, data, iteration_range, predict_type, missing, validate_features, base_margin, strict_shape)\u001b[0m\n\u001b[0;32m   2712\u001b[0m     \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;21;01m.\u001b[39;00m\u001b[38;5;21;01mdata\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m transform_scipy_sparse\n\u001b[0;32m   2714\u001b[0m     data \u001b[38;5;241m=\u001b[39m transform_scipy_sparse(data, \u001b[38;5;28;01mTrue\u001b[39;00m)\n\u001b[0;32m   2715\u001b[0m     _check_call(\n\u001b[1;32m-> 2716\u001b[0m         \u001b[43m_LIB\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mXGBoosterPredictFromCSR\u001b[49m\u001b[43m(\u001b[49m\n\u001b[0;32m   2717\u001b[0m \u001b[43m            \u001b[49m\u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mhandle\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2718\u001b[0m \u001b[43m            \u001b[49m\u001b[43marray_interface\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindptr\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2719\u001b[0m \u001b[43m            \u001b[49m\u001b[43marray_interface\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mindices\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2720\u001b[0m \u001b[43m            \u001b[49m\u001b[43marray_interface\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdata\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2721\u001b[0m \u001b[43m            \u001b[49m\u001b[43mc_bst_ulong\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdata\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mshape\u001b[49m\u001b[43m[\u001b[49m\u001b[38;5;241;43m1\u001b[39;49m\u001b[43m]\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2722\u001b[0m \u001b[43m            \u001b[49m\u001b[43margs\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2723\u001b[0m \u001b[43m            \u001b[49m\u001b[43mp_handle\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2724\u001b[0m \u001b[43m            \u001b[49m\u001b[43mctypes\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbyref\u001b[49m\u001b[43m(\u001b[49m\u001b[43mshape\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2725\u001b[0m \u001b[43m            \u001b[49m\u001b[43mctypes\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbyref\u001b[49m\u001b[43m(\u001b[49m\u001b[43mdims\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2726\u001b[0m \u001b[43m            \u001b[49m\u001b[43mctypes\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mbyref\u001b[49m\u001b[43m(\u001b[49m\u001b[43mpreds\u001b[49m\u001b[43m)\u001b[49m\u001b[43m,\u001b[49m\n\u001b[0;32m   2727\u001b[0m \u001b[43m        \u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m   2728\u001b[0m     )\n\u001b[0;32m   2729\u001b[0m     \u001b[38;5;28;01mreturn\u001b[39;00m _prediction_output(shape, dims, preds, \u001b[38;5;28;01mFalse\u001b[39;00m)\n\u001b[0;32m   2730\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m _is_cupy_alike(data):\n",
      "\u001b[1;31mKeyboardInterrupt\u001b[0m: "
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤8: 模型评估\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 1. 查看模型排行榜\n",
    "leaderboard_optimized = predictor_optimized.leaderboard(silent=True)\n",
    "print(\"\\n模型排行榜 (Top 15):\")\n",
    "print(leaderboard_optimized.head(15))\n",
    "\n",
    "# 2. 最佳模型信息\n",
    "best_model_name = leaderboard_optimized.iloc[0]['model']\n",
    "best_model_score = leaderboard_optimized.iloc[0]['score_val']\n",
    "\n",
    "print(f\"\\n最佳模型: {best_model_name}\")\n",
    "print(f\"验证集 Macro-F1 分数: {best_model_score:.6f}\")\n",
    "\n",
    "# 3. 特征重要性（快速版本）\n",
    "print(f\"\\n计算特征重要性（优化参数，加速计算）...\")\n",
    "try:\n",
    "    feature_importance = predictor_optimized.feature_importance(\n",
    "        data=train_data_ag,\n",
    "        subsample_size=5000,     # 只使用5000个样本\n",
    "        num_shuffle_sets=1,      # 只shuffle 1次\n",
    "        time_limit=120,          # 限制2分钟\n",
    "        silent=False\n",
    "    )\n",
    "    \n",
    "    print(f\"\\nTop 30 重要特征:\")\n",
    "    print(feature_importance.head(30))\n",
    "    \n",
    "    # 保存特征重要性\n",
    "    feature_importance.to_csv('./model/feature_importance_optimized.csv', index=True)\n",
    "    print(f\"\\n特征重要性已保存: ./model/feature_importance_optimized.csv\")\n",
    "    \n",
    "except Exception as e:\n",
    "    print(f\"特征重要性计算失败: {str(e)}\")\n",
    "    print(\"跳过特征重要性分析\")\n",
    "\n",
    "# 4. 模型性能汇总\n",
    "print(f\"\\n\" + \"=\"*100)\n",
    "print(f\"【优化模型】性能汇总\")\n",
    "print(f\"=\"*100)\n",
    "print(f\"验证集 Macro-F1: {best_model_score:.6f}\")\n",
    "print(f\"训练时间: {training_time/60:.2f} 分钟\")\n",
    "print(f\"最终特征数: {X_train.shape[1]}\")\n",
    "print(f\"训练样本数: {X_train.shape[0]}\")\n",
    "print(f\"模型复杂度: medium_quality + 1层Stacking + 1组Bagging\")\n",
    "print(f\"=\"*100)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "82f29a31",
   "metadata": {},
   "source": [
    "## 步骤9：测试集预测"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "e22ccb21",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤9: 测试集预测\n",
      "====================================================================================================\n",
      "\n",
      "开始预测测试集...\n",
      "\n",
      "测试集预测完成!\n",
      "测试集样本数: 5975\n",
      "\n",
      "测试集预测标签分布:\n",
      "  类别 1:     20 ( 0.33%)\n",
      "  类别 2:      5 ( 0.08%)\n",
      "  类别 3:      6 ( 0.10%)\n",
      "  类别 4:   3028 (50.68%)\n",
      "  类别 5:   1013 (16.95%)\n",
      "  类别 6:     70 ( 1.17%)\n",
      "  类别 7:     70 ( 1.17%)\n",
      "  类别 8:    289 ( 4.84%)\n",
      "  类别 9:   1474 (24.67%)\n",
      "\n",
      "训练集与测试集标签分布对比:\n",
      "类别         训练集比例           测试集比例           差异        \n",
      "-------------------------------------------------------\n",
      "1            2.84%           0.33%          -2.50%\n",
      "2            0.60%           0.08%          -0.52%\n",
      "3           10.87%           0.10%         -10.77%\n",
      "4           29.77%          50.68%         +20.90%\n",
      "5           15.94%          16.95%          +1.01%\n",
      "6           19.96%           1.17%         -18.79%\n",
      "7            2.48%           1.17%          -1.31%\n",
      "8            4.86%           4.84%          -0.02%\n",
      "9           12.60%          24.67%         +12.07%\n",
      "10           0.07%           0.00%          -0.07%\n",
      "\n",
      "预测结果已保存: ./model/result_optimized.csv\n",
      "预测结果（带表头）已保存: ./model/result_optimized_with_header.csv\n",
      "\n",
      "测试集预测完成!\n",
      "测试集样本数: 5975\n",
      "\n",
      "测试集预测标签分布:\n",
      "  类别 1:     20 ( 0.33%)\n",
      "  类别 2:      5 ( 0.08%)\n",
      "  类别 3:      6 ( 0.10%)\n",
      "  类别 4:   3028 (50.68%)\n",
      "  类别 5:   1013 (16.95%)\n",
      "  类别 6:     70 ( 1.17%)\n",
      "  类别 7:     70 ( 1.17%)\n",
      "  类别 8:    289 ( 4.84%)\n",
      "  类别 9:   1474 (24.67%)\n",
      "\n",
      "训练集与测试集标签分布对比:\n",
      "类别         训练集比例           测试集比例           差异        \n",
      "-------------------------------------------------------\n",
      "1            2.84%           0.33%          -2.50%\n",
      "2            0.60%           0.08%          -0.52%\n",
      "3           10.87%           0.10%         -10.77%\n",
      "4           29.77%          50.68%         +20.90%\n",
      "5           15.94%          16.95%          +1.01%\n",
      "6           19.96%           1.17%         -18.79%\n",
      "7            2.48%           1.17%          -1.31%\n",
      "8            4.86%           4.84%          -0.02%\n",
      "9           12.60%          24.67%         +12.07%\n",
      "10           0.07%           0.00%          -0.07%\n",
      "\n",
      "预测结果已保存: ./model/result_optimized.csv\n",
      "预测结果（带表头）已保存: ./model/result_optimized_with_header.csv\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤9: 测试集预测\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 预测测试集\n",
    "print(\"\\n开始预测测试集...\")\n",
    "test_pred_optimized = predictor_optimized.predict(test_data_ag)\n",
    "test_pred_proba_optimized = predictor_optimized.predict_proba(test_data_ag)\n",
    "\n",
    "print(f\"\\n测试集预测完成!\")\n",
    "print(f\"测试集样本数: {len(test_pred_optimized)}\")\n",
    "\n",
    "# 测试集预测标签分布\n",
    "print(f\"\\n测试集预测标签分布:\")\n",
    "test_pred_dist_optimized = pd.Series(test_pred_optimized).value_counts().sort_index()\n",
    "for label, count in test_pred_dist_optimized.items():\n",
    "    rate = count / len(test_pred_optimized) * 100\n",
    "    print(f\"  类别 {label}: {count:6d} ({rate:5.2f}%)\")\n",
    "\n",
    "# 与训练集分布对比\n",
    "print(f\"\\n训练集与测试集标签分布对比:\")\n",
    "print(f\"{'类别':<10} {'训练集比例':<15} {'测试集比例':<15} {'差异':<10}\")\n",
    "print(\"-\" * 55)\n",
    "\n",
    "train_label_dist = y_train.value_counts().sort_index()\n",
    "for label in sorted(train_label_dist.index):\n",
    "    train_rate = train_label_dist[label] / len(y_train) * 100\n",
    "    test_rate = test_pred_dist_optimized.get(label, 0) / len(test_pred_optimized) * 100\n",
    "    diff = test_rate - train_rate\n",
    "    print(f\"{label:<10} {train_rate:>6.2f}%{'':<8} {test_rate:>6.2f}%{'':<8} {diff:>+6.2f}%\")\n",
    "\n",
    "# 创建提交结果\n",
    "result_optimized_df = pd.DataFrame({\n",
    "    'CUST_NO': test_cust_no,\n",
    "    'FLAG': test_pred_optimized\n",
    "})\n",
    "\n",
    "# 保存预测结果\n",
    "result_file = './model/result_optimized.csv'\n",
    "result_optimized_df.to_csv(result_file, index=False, header=False)\n",
    "print(f\"\\n预测结果已保存: {result_file}\")\n",
    "\n",
    "# 保存带表头版本（便于查看）\n",
    "result_file_with_header = './model/result_optimized_with_header.csv'\n",
    "result_optimized_df.to_csv(result_file_with_header, index=False, header=True)\n",
    "print(f\"预测结果（带表头）已保存: {result_file_with_header}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "abe36dc4",
   "metadata": {},
   "source": [
    "## 步骤10：验证集评估（如果有验证集）"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 19,
   "id": "142179df",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "====================================================================================================\n",
      "步骤10: 验证集评估（如果有验证集）\n",
      "====================================================================================================\n",
      "\n",
      "发现验证集文件: ./A_TARGET_VALID.csv\n",
      "验证集样本数: 5975\n",
      "匹配成功样本数: 5975\n",
      "匹配率: 100.00%\n",
      "\n",
      "====================================================================================================\n",
      "【优化模型】验证集 Macro-F1 分数: 0.391707\n",
      "【优化模型】内部验证集 Macro-F1: 0.586511\n",
      "【优化模型】差异: 0.194804\n",
      "====================================================================================================\n",
      "\n",
      "验证集详细分类报告:\n",
      "              precision    recall  f1-score   support\n",
      "\n",
      "           1     0.5500    0.0651    0.1164       169\n",
      "           2     0.0000    0.0000    0.0000        73\n",
      "           3     0.0000    0.0000    0.0000         0\n",
      "           4     0.8098    0.8209    0.8153      2987\n",
      "           5     0.9230    0.8755    0.8986      1068\n",
      "           6     0.0000    0.0000    0.0000         0\n",
      "           7     0.7857    0.4015    0.5314       137\n",
      "           8     0.8720    0.9000    0.8858       280\n",
      "           9     0.6187    0.7296    0.6696      1250\n",
      "          10     0.0000    0.0000    0.0000        11\n",
      "\n",
      "    accuracy                         0.7727      5975\n",
      "   macro avg     0.4559    0.3793    0.3917      5975\n",
      "weighted avg     0.7737    0.7727    0.7653      5975\n",
      "\n",
      "\n",
      "混淆矩阵:\n",
      "[[  11    0    0   94    3    0    1    0   60    0]\n",
      " [   0    0    1   33    0    0    0    0   39    0]\n",
      " [   0    0    0    0    0    0    0    0    0    0]\n",
      " [   9    2    2 2452   44   40    6   25  407    0]\n",
      " [   0    0    0   99  935    3    3    9   19    0]\n",
      " [   0    0    0    0    0    0    0    0    0    0]\n",
      " [   0    0    0   40    4    1   55    1   36    0]\n",
      " [   0    0    0   20    6    1    1  252    0    0]\n",
      " [   0    3    3  280   21   25    4    2  912    0]\n",
      " [   0    0    0   10    0    0    0    0    1    0]]\n",
      "\n",
      "混淆矩阵图已保存: ./model/confusion_matrix_valid_optimized.png\n",
      "\n",
      "混淆矩阵图已保存: ./model/confusion_matrix_valid_optimized.png\n"
     ]
    },
    {
     "data": {
      "image/png": "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",
      "text/plain": [
       "<Figure size 1000x800 with 2 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "验证集标签分布对比:\n",
      "类别         真实数量            预测数量            真实比例            预测比例           \n",
      "---------------------------------------------------------------------------\n",
      "1             169              20            2.83%           0.33%\n",
      "2              73               5            1.22%           0.08%\n",
      "3               0               6            0.00%           0.10%\n",
      "4            2987            3028           49.99%          50.68%\n",
      "5            1068            1013           17.87%          16.95%\n",
      "6               0              70            0.00%           1.17%\n",
      "7             137              70            2.29%           1.17%\n",
      "8             280             289            4.69%           4.84%\n",
      "9            1250            1474           20.92%          24.67%\n",
      "10             11               0            0.18%           0.00%\n",
      "\n",
      "验证结果已保存: ./model/validation_result_optimized.csv\n"
     ]
    }
   ],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"步骤10: 验证集评估（如果有验证集）\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "# 检查是否存在验证集文件\n",
    "valid_file = './A_TARGET_VALID.csv'\n",
    "\n",
    "if os.path.exists(valid_file):\n",
    "    print(f\"\\n发现验证集文件: {valid_file}\")\n",
    "    \n",
    "    # 加载验证集\n",
    "    valid_data = pd.read_csv(valid_file)\n",
    "    print(f\"验证集样本数: {len(valid_data)}\")\n",
    "    \n",
    "    # 合并预测结果和验证集真实标签\n",
    "    result_valid_merged = result_optimized_df.merge(\n",
    "        valid_data, \n",
    "        on='CUST_NO', \n",
    "        how='inner',\n",
    "        suffixes=('_pred', '_true')\n",
    "    )\n",
    "    \n",
    "    print(f\"匹配成功样本数: {len(result_valid_merged)}\")\n",
    "    print(f\"匹配率: {len(result_valid_merged)/len(valid_data)*100:.2f}%\")\n",
    "    \n",
    "    # 提取预测标签和真实标签\n",
    "    y_pred_valid = result_valid_merged['FLAG_pred']\n",
    "    y_true_valid = result_valid_merged['FLAG_true']\n",
    "    \n",
    "    # 计算Macro-F1分数\n",
    "    from sklearn.metrics import f1_score, classification_report, confusion_matrix\n",
    "    macro_f1_valid = f1_score(y_true_valid, y_pred_valid, average='macro')\n",
    "    \n",
    "    print(f\"\\n\" + \"=\"*100)\n",
    "    print(f\"【优化模型】验证集 Macro-F1 分数: {macro_f1_valid:.6f}\")\n",
    "    print(f\"【优化模型】内部验证集 Macro-F1: {best_model_score:.6f}\")\n",
    "    print(f\"【优化模型】差异: {abs(macro_f1_valid - best_model_score):.6f}\")\n",
    "    print(f\"=\"*100)\n",
    "    \n",
    "    # 详细分类报告\n",
    "    print(f\"\\n验证集详细分类报告:\")\n",
    "    print(classification_report(y_true_valid, y_pred_valid, digits=4))\n",
    "    \n",
    "    # 混淆矩阵\n",
    "    conf_matrix_valid = confusion_matrix(y_true_valid, y_pred_valid)\n",
    "    print(f\"\\n混淆矩阵:\")\n",
    "    print(conf_matrix_valid)\n",
    "    \n",
    "    # 可视化混淆矩阵\n",
    "    plt.figure(figsize=(10, 8))\n",
    "    sns.heatmap(conf_matrix_valid, annot=True, fmt='d', cmap='Blues')\n",
    "    plt.xlabel('Predicted')\n",
    "    plt.ylabel('Actual')\n",
    "    plt.title(f'Confusion Matrix - Optimized Model (Macro-F1: {macro_f1_valid:.6f})')\n",
    "    plt.tight_layout()\n",
    "    plt.savefig('./model/confusion_matrix_valid_optimized.png', dpi=150, bbox_inches='tight')\n",
    "    print(f\"\\n混淆矩阵图已保存: ./model/confusion_matrix_valid_optimized.png\")\n",
    "    plt.show()\n",
    "    \n",
    "    # 各类别预测统计\n",
    "    print(f\"\\n验证集标签分布对比:\")\n",
    "    print(f\"{'类别':<10} {'真实数量':<15} {'预测数量':<15} {'真实比例':<15} {'预测比例':<15}\")\n",
    "    print(\"-\" * 75)\n",
    "    \n",
    "    true_dist = y_true_valid.value_counts().sort_index()\n",
    "    pred_dist = y_pred_valid.value_counts().sort_index()\n",
    "    \n",
    "    for label in sorted(set(list(true_dist.index) + list(pred_dist.index))):\n",
    "        true_count = true_dist.get(label, 0)\n",
    "        pred_count = pred_dist.get(label, 0)\n",
    "        true_rate = true_count / len(y_true_valid) * 100\n",
    "        pred_rate = pred_count / len(y_pred_valid) * 100\n",
    "        print(f\"{label:<10} {true_count:>6d}{'':<9} {pred_count:>6d}{'':<9} {true_rate:>6.2f}%{'':<8} {pred_rate:>6.2f}%\")\n",
    "    \n",
    "    # 保存验证结果\n",
    "    valid_result_file = './model/validation_result_optimized.csv'\n",
    "    result_valid_merged.to_csv(valid_result_file, index=False)\n",
    "    print(f\"\\n验证结果已保存: {valid_result_file}\")\n",
    "    \n",
    "else:\n",
    "    print(f\"\\n未发现验证集文件: {valid_file}\")\n",
    "    print(\"跳过验证集评估\")\n",
    "    print(\"\\n提示: 如需验证模型效果，请准备验证集文件（包含CUST_NO和FLAG列）\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c9fe5975",
   "metadata": {},
   "source": [
    "# 总结与对比分析"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "02773b0d",
   "metadata": {},
   "source": [
    "## 改进效果总结\n",
    "\n",
    "本次优化主要采取了以下措施：\n",
    "\n",
    "### 核心改进点\n",
    "\n",
    "1. **特征工程优化**\n",
    "   - 移除常量特征\n",
    "   - 移除高缺失率特征（>80%）\n",
    "   - 移除低方差特征（<0.01）\n",
    "   - 移除高相关特征（相关系数>0.95）\n",
    "   - 统一缺失值填充策略\n",
    "\n",
    "2. **模型简化**\n",
    "   - 从best_quality降为medium_quality\n",
    "   - Stacking层数：2层→1层\n",
    "   - Bagging组数：2组→1组\n",
    "   - 排除易过拟合模型：KNN、神经网络\n",
    "\n",
    "3. **正则化增强**\n",
    "   - 提高L1/L2正则化系数（0.1→0.5）\n",
    "   - 限制树深度（无限制→5-7层）\n",
    "   - 降低学习率（0.05→0.02-0.03）\n",
    "   - 增加最小样本数（10-20→25-30）\n",
    "   - 降低特征/样本采样比例（0.8-0.9→0.7-0.75）\n",
    "\n",
    "### 预期改进效果\n",
    "\n",
    "**原始模型问题：**\n",
    "- 训练集Macro-F1: 0.606428\n",
    "- 验证集Macro-F1: 0.442329\n",
    "- 差异: 0.164099（严重过拟合）\n",
    "\n",
    "**优化目标：**\n",
    "- 验证集Macro-F1: 0.48-0.52（提升8-10个百分点）\n",
    "- 过拟合差异: <0.10（降低至10%以内）\n",
    "- 泛化能力显著提升\n",
    "\n",
    "### 关键监控指标\n",
    "\n",
    "运行完成后，请重点关注以下指标：\n",
    "1. **验证集Macro-F1分数** - 是否有提升\n",
    "2. **过拟合程度** - 内部验证vs外部验证的差异是否缩小\n",
    "3. **各类别F1分数** - 是否更均衡\n",
    "4. **特征数量** - 是否有效减少\n",
    "5. **训练时间** - 是否更高效\n",
    "\n",
    "### 后续优化方向\n",
    "\n",
    "如果本次优化效果仍不理想，可以考虑：\n",
    "1. 进一步减少特征（基于特征重要性Top-K选择）\n",
    "2. 增加时间序列特征工程\n",
    "3. 尝试类别特征的Target Encoding\n",
    "4. 调整类别权重或使用SMOTE\n",
    "5. 使用时间感知的交叉验证策略"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e17b2b62",
   "metadata": {},
   "outputs": [],
   "source": [
    "print(\"=\"*100)\n",
    "print(\"优化方案实施总结\")\n",
    "print(\"=\"*100)\n",
    "\n",
    "summary = \"\"\"\n",
    "基于对原始模型的深度分析，识别出以下核心问题：\n",
    "\n",
    "1. 严重过拟合（训练集0.606 vs 验证集0.442，差异16.4%）\n",
    "2. 特征维度过高，存在大量冗余和低质量特征\n",
    "3. 模型复杂度过高（2层Stacking + 2组Bagging + best_quality）\n",
    "4. 缺乏有效的正则化措施\n",
    "\n",
    "本次优化采取的核心策略：\n",
    "\n",
    "【特征工程优化】\n",
    "- 移除常量特征、高缺失特征、低方差特征\n",
    "- 移除高相关特征（相关系数>0.95）\n",
    "- 统一缺失值填充策略\n",
    "- 目标：将特征数从原始规模压缩50-70%\n",
    "\n",
    "【模型简化】\n",
    "- preset: best_quality → medium_quality\n",
    "- Stacking层数: 2层 → 1层  \n",
    "- Bagging组数: 2组 → 1组\n",
    "- 排除易过拟合模型: KNN、NN_TORCH、FASTAI\n",
    "- 目标：降低模型复杂度，减少过拟合风险\n",
    "\n",
    "【正则化增强】\n",
    "- L1/L2系数: 0.1-0.2 → 0.4-0.5（提升2-3倍）\n",
    "- 树深度: 无限制 → 5-7层\n",
    "- 学习率: 0.05 → 0.02-0.03（降低40-60%）\n",
    "- 最小样本数: 10-20 → 25-30（提升50-100%）\n",
    "- 采样比例: 0.8-0.9 → 0.7-0.75（降低10-20%）\n",
    "- 目标：增强模型泛化能力\n",
    "\n",
    "预期效果：\n",
    "- 验证集Macro-F1提升至0.48-0.52（+8-10个百分点）\n",
    "- 过拟合差异降至<10%\n",
    "- 训练时间控制在1小时内\n",
    "- 模型更稳定、更可解释\n",
    "\n",
    "请运行上述代码单元，观察优化效果！\n",
    "\"\"\"\n",
    "\n",
    "print(summary)\n",
    "print(\"=\"*100)"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "starcup",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
